/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: proxy.c,v 1.41 2006/02/18 20:30:22 pullmoll Exp $
 *****************************************************************************/
#include "proxy.h"
#include "logger.h"

#define	LINESIZE		4096

/*
 * In the proxy code, the LO() language is defined by reply->language,
 * while the character set is defined by reply->charset
 */
#undef	LANG
#define	LANG	reply->language
#undef	CHARSET
#define	CHARSET	reply->charset

#define	FROST_MSG			"===Frost signed message==="
#define	FROST_MSG_LEN		(sizeof(FROST_MSG)-1)
#define	FROST_KEY			"<key>"
#define	FROST_KEY_LEN		(sizeof(FROST_KEY)-1)
#define	FROST_EOK			"</key>"
#define	FROST_EOK_LEN		(sizeof(FROST_EOK)-1)
#define	FROST_SIG			"=== Frost message signature: ==="
#define	FROST_SIG_LEN		(sizeof(FROST_SIG)-1)
#define	FROST_EOS			"=== End of Signature. ==="
#define	FROST_EOS_LEN		(sizeof(FROST_EOS)-1)

#define	ATTACHED1_BEG		"<attached>"
#define	ATTACHED1_BEG_LEN	(sizeof(ATTACHED1_BEG)-1)
#define	ATTACHED1_MID		" * "
#define	ATTACHED1_MID_LEN	(sizeof(ATTACHED1_MID)-1)
#define	ATTACHED1_END		"</attached>"
#define	ATTACHED1_END_LEN	(sizeof(ATTACHED1_END)-1)

#define	ATTACHED2_BEG		"<!attached>"
#define	ATTACHED2_BEG_LEN	(sizeof(ATTACHED2_BEG)-1)
#define	ATTACHED2_MID		" * "
#define	ATTACHED2_MID_LEN	(sizeof(ATTACHED2_MID)-1)
#define	ATTACHED2_END		"<!/attached>"
#define	ATTACHED2_END_LEN	(sizeof(ATTACHED2_END)-1)

#define	REPLY_HDR			"----- "
#define	REPLY_HDR_LEN		(sizeof(REPLY_HDR)-1)
#define	REPLY_MID			" ----- "
#define	REPLY_MID_LEN		(sizeof(REPLY_MID)-1)
#define	REPLY_END			" -----"
#define	REPLY_END_LEN		(sizeof(REPLY_END)-1)

#define	RED		"\033[0;31m"
#define	GREEN	"\033[0;32m"
#define	BROWN	"\033[0;33m"
#define	BLUE	"\033[0;34m"
#define	HILITE	"\033[0;1m"
#define	CLRSCR	"\033[2J"
#define	CAPTION	"\033[1;1H\033[44;37m\033[2K"
#define	REGION	"\033[3;24r\033[3;1H"
#define	CLICK	"\033[s\033[1;1H\033[0;42m\033[2K\t\t\t\tC L I C K\033[u"
#define	CLREOL	"\033[0K"

proxy_t *g_proxy = NULL;

#define	PROXY_CACHE_LOCK()		osd_sem_wait(&g_proxy->sem)
#define	PROXY_CACHE_UNLOCK()	osd_sem_post(&g_proxy->sem)

pid_t g_proxy_pid = (pid_t)-1;

#define	DOCTYPE \
	"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
#define CONTENT_TYPE \
	"  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%%charset%%\"/>\n" 
#define STYLESHEET \
	"<style type=\"text/css\">\n"  \
	"<!--\n" \
    "%%stylesheet%%" \
	"-->\n" \
	"</style>\n"

enum {
	RESULT_ERROR = -1,
	RESULT_SUCCESS,
	RESULT_CREATED,
	RESULT_PENDING
};

enum {
	INSERT_PENDING = 0,
	INSERT_SUCCESS,
	INSERT_COLLISION,
	INSERT_RNF,
	INSERT_DNF,
	INSERT_URI,
	INSERT_NODEPUT
};

typedef struct proxy_post_var_s {
	char name[64];
	char content_type[64];
	char filename[MAXPATHLEN];
	char value[256];
	size_t offs;
	size_t size;
}	proxy_post_var_t;

enum {
	CHARSET_ISO8859_1,
	CHARSET_ISO8859_2,
	CHARSET_ISO8859_3,
	CHARSET_ISO8859_4,
	CHARSET_ISO8859_5,
	CHARSET_ISO8859_7,
	CHARSET_ISO8859_8,
	CHARSET_ISO8859_9,
	CHARSET_ISO8859_10,
	CHARSET_ISO8859_13,
	CHARSET_ISO8859_14,
	CHARSET_ISO8859_15,
	CHARSET_JISX201_1976,
	CHARSET_KOI8_R,
	CHARSET_CP437,
	CHARSET_UTF8
};

typedef struct proxy_charset_s {
	int cset;
	const char *name;
}	proxy_charset_t;

proxy_charset_t charsets[] = {
	{CHARSET_ISO8859_1,		"iso-8859-1"},
	{CHARSET_ISO8859_2,		"iso-8859-2"},
	{CHARSET_ISO8859_3,		"iso-8859-3"},
	{CHARSET_ISO8859_4,		"iso-8859-4"},
	{CHARSET_ISO8859_5,		"iso-8859-5"},
	{CHARSET_ISO8859_7,		"iso-8859-7"},
	{CHARSET_ISO8859_8,		"iso-8859-8"},
	{CHARSET_ISO8859_9,		"iso-8859-9"},
	{CHARSET_ISO8859_10,	"iso-8859-10"},
	{CHARSET_ISO8859_13,	"iso-8859-13"},
	{CHARSET_ISO8859_14,	"iso-8859-14"},
	{CHARSET_ISO8859_15,	"iso-8859-15"},
	{CHARSET_JISX201_1976,	"jisx201-1976"},
	{CHARSET_KOI8_R,		"koi8-r"},
	{CHARSET_CP437,			"cp437"},
	{CHARSET_UTF8,			"utf-8"}
};

enum http_method {
	NONE, HTTP_GET, HTTP_HEAD, HTTP_POST
};

#define	NUM_PROTOCOLS	6
typedef struct protocol_s {
	size_t proto_len;
	const char *proto;
	size_t name_len;
	const char *name;
}	protocol_t;

static protocol_t filter_protocol[NUM_PROTOCOLS] = {
	{6, "ftp://",    3, "FTP"},
	{9, "gopher://", 6, "GOPHER"},
	{7, "http://",   4, "HTTP"},
	{8, "https://",  5, "HTTPS"},
	{9, "telnet://", 6, "TELNET"},
	{7, "rtsp://",   4, "RTSP"}
};

static const char *nav(proxy_reply_t *reply, const char *class, const char *fmt, ...);

/*
 *	digit()
 *	Return the value of a hex digit (%xx URL encoding)
 */
static int digit(int c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	return 0;
}

static int str_replace(char *haystack, size_t size,
	const char *needle, const char *src)
{
	size_t hlen = strlen(haystack);
	int offs, diff, slen;
	char *dst = haystack;
	FUN("str_replace");

	if (NULL == needle || NULL == src)
		return 0;

	slen = strlen(src);
	diff = slen - strlen(needle);
	while (NULL != (dst = strstr(dst, needle))) {
		offs = (int)(dst - haystack);
		if (diff < 0) {
			memmove(&haystack[offs], &haystack[offs-diff], hlen + 1 - offs);
			hlen -= diff;
		} else if (diff > 0) {
			if (hlen + diff > size) {
				LOGS(L_PROXY,L_MINOR,("out of memory (have: %#x, need: %#x)\n",
					(unsigned)size, (unsigned)(hlen + diff)));
				errno = ENOMEM;
				return -1;
			}
			memmove(&haystack[offs+diff], &haystack[offs], hlen + 1 - offs);
			hlen += diff;
		}
		memcpy(dst, src, slen);
		dst += slen;
	}
	return 0;
}

int proxy_stylesheet(proxy_reply_t *reply)
{
	char *style_css = NULL;
	struct stat st;
	int fd = -1;
	FUN("proxy_stylesheet");

	xfree(reply->stylesheet);
	pm_asprintf(&style_css, "%s/node/style.css", g_conf->configpath);

	if (0 == stat(style_css, &st) && S_ISREG(st.st_mode)) {
		fd = open(style_css, O_RDONLY|O_BINARY);
		reply->stylesheet = xcalloc(st.st_size + 1, sizeof(char));
		read(fd, reply->stylesheet, st.st_size);
		close(fd);
		xfree(style_css);
		return 0;
	}
	xfree(style_css);

	reply->stylesheet = xstrdup(
"h1,h2,h3,h4,p,ul,ol,li,div,td,th,address,blockquote,nobr,b,i {\n" \
" font-family:Verdana,Helvetica,Arial,sans-serif; }\n" \
"ul, li { margin-top:3px; }\n" \
"pre { font-family:Courier New,Courier,fixed,serif; white-space:normal; }\n" \
"code,kbd,tt { font-family:Courier New,Courier,fixed,serif; color:#000060; }\n" \
"blockquote,cite { font-style:italic; }\n" \
"var { font-style:normal; color:#000060; }\n" \
"body { padding-left:20px; background:url(/node/background.png) repeat-y; }\n" \
"a:link { color:#ffffff; text-decoration:underline; }\n" \
"a:visited { color:#fcfcfc; text-decoration:underline; }\n" \
"a:hover { color:#ff003f; text-decoration:underline; }\n" \
"a:focus { color:#ff003f; text-decoration:underline; }\n" \
"a:active { color:#000000; text-decoration:none; }\n" \
".left { text-align:left; vertical-align:top; }\n" \
".center { text-align:center; vertical-align:top; }\n" \
".right { text-align:right; vertical-align:top; }\n" \
".title { text-align:center; font-size:bigger; font-weight:bold;\n" \
"	color:#ffffff; background:#0077aa; padding:2px;\n" \
"	border:1px solid #0077aa; }\n" \
".subtitle { text-align:center; font-size:big; font-weight:bold;\n" \
"	color:#ffffff; background:#0088bb; padding:1px;\n" \
"	border:1px solid #0088bb; }\n" \
".warning { text-align:center; font-size:bigger; font-weight:bold;\n" \
"	background:#f8f0af;\n" \
"	border:1px solid #f8f0af; }\n" \
".error { text-align:center; font-size:bigger; font-weight:bold;\n" \
"	background:#f8c07f; color:#e04000;\n" \
"	border:1px solid #f8c07f; }\n" \
".menue { text-align:center; font-size:smaller; font-weight:bold;\n" \
"	color:#ffffff; background:#006699; color:#ffffff;\n" \
"	border:1px solid #006699; }\n" \
".head { background:#0077aa; color:#ffffff; }\n" \
".body { background:#ffffff; color:#000000; }\n" \
".body a:link { color:#0000c0; text-decoration:underline; }\n" \
".body a:visited { color:#00a0a0; text-decoration:underline; }\n" \
".body a:hover { color:#ff003f; text-decoration:underline; }\n" \
".body a:focus { color:#ff003f; text-decoration:underline; }\n" \
".body a:active { color:#000000; text-decoration:none; }\n" \
".foot { background:#ffffff; color:#000000; }\n" \
".even { background:#e0e0ff; }\n" \
".odd { background:#ffffff; }\n" \
".var { text-align:left; font-size:0.8em; font-weight:bold;\n" \
"	border:1px outset; }\n" \
".var a:link { color:#000000; text-decoration:underline; }\n" \
".var a:visited { color:#000000; text-decoration:underline; }\n" \
".val { text-align:right; font-size:0.8em; font-weight:thin;\n" \
"	border:1px outset; }\n" \
".in { background:#a0ffa0; border:2px outset #00af00; }\n" \
".out { background:#ffa0a0; border:2px outset #af0000; }\n" \
".button { text-align:center; vertical-align:top;\n" \
"	font-size:small; font-weight:bold;\n" \
"	background:#004070; color:#a0a0a0; white-space:nowrap;\n" \
"	border:1px outset #004070;\n" \
"	padding:1px; }\n" \
".label { text-align:center; vertical-align:top;\n" \
"	font-size:medium; font-weight:bold;\n" \
"	background:#0070a0; color:#ffffff; white-space:nowrap;\n" \
"	border:1px outset #0070a0;\n" \
"	padding:1px; padding-left:4px; padding-right:4px; }\n" \
".content { text-align:left; vertical-align:top;\n" \
"	font-size:medium; font-weight:normal;\n" \
"	background:#005080; color:#ffffff; white-space:nowrap;\n" \
"	border:1px outset #005080;\n" \
"	padding:1px; padding-left:4px; padding-right:4px; }\n" \
".hilite { text-align:left; vertical-align:top;\n" \
"	font-size:medium; font-weight:normal;\n" \
"	background:#0090b0; color:#ffffff; white-space:nowrap;\n" \
"	border:1px outset #0090b0;\n" \
"	padding:1px; padding-left:4px; padding-right:4px; }\n" \
".message { text-align:left; vertical-align:top; \n" \
"	font-size:medium; font-weight:normal;\n" \
"	background:#effeff; color:#000060; white-space:normal;\n" \
"	border:1px outset #effeff;\n" \
"	padding:8px; }\n");

	return 0;
}

static char *cleanup(const char *src)
{
	char *clean = NULL;
	size_t size;

	size = 1;
	while (size < strlen(src) + 1)
		size <<= 1;
	for (;;) {
		/* increase size to have some space for replacements */
		size <<= 1;
		clean = xrealloc(clean, size + 1);
		strncpy(clean, src, size);
		if (0 != str_replace(clean, size, "&", "&amp;"))
			continue;
		if (0 != str_replace(clean, size, "<", "&lt;"))
			continue;
		if (0 != str_replace(clean, size, ">", "&gt;"))
			continue;
		break;
	}
	return clean;
}

int proxy_chat_output(proxy_reply_t *reply)
{
	char *output = NULL;
	int id, min, max;
	char channel[MAXCHANNEL];
	char sender[MAXSENDER];
	char payload[MAXPAYLOAD];
	char *channel_clean = NULL;
	char *sender_clean = NULL;
	char *payload_clean = NULL;
	char *color;
	time_t t0;
	struct tm tm;
	size_t len, app;
	FUN("proxy_chat_output");

	xfree(reply->chat_output);
	if (0 != peer_get_broadcast(0, &min, &max, NULL, NULL, NULL, NULL, NULL)) {
		reply->chat_output = xstrdup("No broadcasts\n");
		return -1;
	}
	/* newest broadcast first */
	for (id = max; id >= min; id--) {
		if (0 == id)
			continue;
		if (0 == peer_get_broadcast(id, &min, &max, NULL, &t0, channel, sender, payload)) {
			channel_clean = cleanup(channel);
			sender_clean = cleanup(sender);
			payload_clean = cleanup(payload);
			memcpy(&tm, gmtime(&t0), sizeof(tm));
			if (NULL == output)
				len = 0;
			else
				len = strlen(output);
			color = strchr(sender_clean, '#');
			if (NULL != color) {
				*color++ = '\0';
				app = 32 + 32 + strlen(color) +
					32 + strlen(channel_clean) + 1 +
					strlen(sender_clean) + 1 +
					strlen(payload_clean) + 6 + 1;
				output = xrealloc(output, len + app);
				pm_snprintf(&output[len], app, "<div class=\"%s\">%04u-%02u-%02u %02u:%02u:%02u/%s/<font color=\"#%s\">%s: %s</font></div>\n",
					(const char *)((id & 1) ? "odd" : "even"),
					1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
					tm.tm_hour, tm.tm_min, tm.tm_sec,
					channel_clean, color, sender_clean, payload_clean);
			} else {
				app = 32 + 32 + strlen(channel_clean) + 1 +
					strlen(sender_clean) + 1 +
					strlen(payload_clean) + 6 + 1;
				output = xrealloc(output, len + app);
				pm_snprintf(&output[len], app, "<div class=\"%s\">%04u-%02u-%02u %02u:%02u:%02u/%s/%s: %s</div>\n",
					(const char *)((id & 1) ? "odd" : "even"),
					1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
					tm.tm_hour, tm.tm_min, tm.tm_sec,
					channel_clean, sender_clean, payload_clean);
			}
			xfree(channel_clean);
			xfree(sender_clean);
			xfree(payload_clean);
		}
	}
	if (NULL == output)
		output = xstrdup("No broadcasts\n");
	reply->chat_output = output;
	LOG(L_MINOR,("chat_output:\n%s\n", reply->chat_output));
	xfree(payload_clean);
	return 0;
}

int proxy_chat_status(proxy_reply_t *reply)
{
	char *status = NULL;
	int id, min, max;
	char sender[MAXSENDER];
	char *sender_clean;
	char *color;
	size_t len, app;
	FUN("proxy_chat_status");

	xfree(reply->chat_status);
	if (0 != peer_get_broadcast(0, &min, &max, NULL, NULL, NULL, NULL, NULL)) {
		reply->chat_status = xstrdup("-/-\n");
		return -1;
	}
	for (id = max; id >= min; id--) {
		if (0 == id)
			continue;
		if (0 == peer_get_broadcast(id, &min, &max, NULL, NULL, NULL, sender, NULL)) {
			sender_clean = cleanup(sender);
			if (NULL == status)
				len = 0;
			else
				len = strlen(status);
			color = strchr(sender_clean, '#');
			if (NULL != color) {
				*color++ = '\0';
				if (len > 0 && NULL != strstr(status, sender_clean))
					continue;
				app = 32 + strlen(color) +
					strlen(sender_clean) + 6 + 1;
				status = xrealloc(status, len + app);
				pm_snprintf(&status[len], app, "<font color=\"#%s\">%s</font><br />\n",
					color, sender_clean);
			} else {
				if (len > 0 && NULL != strstr(status, sender_clean))
					continue;
				app = strlen(sender_clean) + 6 + 1;
				status = xrealloc(status, len + app);
				pm_snprintf(&status[len], app, "%s<br />\n",
					sender_clean);
			}
			xfree(sender_clean);
		}
	}
	if (NULL == status)
		status = xstrdup("-/-\n");
	reply->chat_status = status;
	LOG(L_MINOR,("chat_status:\n%s\n", reply->chat_status));
	return 0;
}

void html_vars(proxy_reply_t *reply, char **pstr)
{
	size_t size, len;
	char *dst = NULL;
	char *src = NULL;
	char *tmp = NULL;
	peer_load_t load;
	char *alt_loadgraphics = NULL;
	char *alt_latestbuild = NULL;
	char *img_loadgraphics = NULL;
	char *img_latestbuild = NULL;
	const char *navigation = NULL;
	char title[128];
	char color[3*2+1];
	FUN("html_vars");

	/* load or set the stylesheet */
	if (NULL != strstr(*pstr, "%stylesheet%"))
		proxy_stylesheet(reply);
	/* collect chat output */
	if (NULL != strstr(*pstr, "%chat_output%"))
		proxy_chat_output(reply);
	/* collect chat status */
	if (NULL != strstr(*pstr, "%chat_status%"))
		proxy_chat_status(reply);
	if (NULL != (src = strstr(*pstr, "%nav"))) {
		if (src[4] == '(') {
			dst = title;
			tmp = src + 4;
			src = src + 5;
			while (*src != '\0' && *src != ')') {
				if (dst < &title[127])
					*dst++ = *src;
				src++;
			}
			*dst = '\0';
			dst = NULL;
			if (*src == ')')
				src++;
			strcpy(tmp, src);
		} else {
			pm_snprintf(title, sizeof(title), LO("Entropy"));
		}
		navigation = nav(reply, "subtitle", title);
	}
	pm_snprintf(color, sizeof(color), "%06x",
		(unsigned)(reply->chat_color & 0xffffff));
	peer_load(&load);
	pm_asprintf(&alt_loadgraphics, "{%d%%}",
		load.estimated_load);
	pm_asprintf(&alt_latestbuild, "{%u.%u.%u-%u}",
		g_conf->latest_major,
		g_conf->latest_minor / 65536,
		g_conf->latest_minor % 65536,
		g_conf->latest_build);

	pm_asprintf(&img_loadgraphics,
		"<img src=\"/node/loadgraphics%02x.png\"" \
		" width=\"100\" height=\"16\" border=\"0\" alt=\"%s\" />",
		load.estimated_load, alt_loadgraphics);

	pm_asprintf(&img_latestbuild,
		"<img src=\"/node/latestbuild.png\"" \
		" width=\"100\" height=\"16\" border=\"0\" alt=\"%s\" />",
		alt_latestbuild);

	size = 128;
	while (size < strlen(*pstr) + 1)
		size <<= 1;

	for (;;) {
		/* increase size to have some space for replacements */
		size <<= 1;
		dst = xrealloc(dst, size + 1);
		strncpy(dst, *pstr, size);

		if (0 != str_replace(dst, size, "%title%",
			NODE_NAME " Gateway"))
			continue;
		if (0 != str_replace(dst, size, "%version%", PACKAGE_VERSION))
			continue;
		if (0 != str_replace(dst, size, "%header1%",
			NODE_NAME " " PACKAGE_VERSION))
			continue;
		if (0 != str_replace(dst, size, "%program%",
			NODE_NAME))
			continue;
		if (NULL != reply->charset) {
			if (0 != str_replace(dst, size, "%charset%",
				reply->charset))
				continue;
		} else {
			if (0 != str_replace(dst, size, "%charset%",
				"iso-8859-1"))
				continue;
		}
		if (0 != str_replace(dst, size, "%insert%",
			LO("insert")))
			continue;
		if (0 != str_replace(dst, size, "%request%",
			LO("request")))
			continue;

		if (0 != str_replace(dst, size, "%stylesheet%", reply->stylesheet))
			continue;
		if (0 != str_replace(dst, size, "%nav%", navigation))
			continue;
		if (0 != str_replace(dst, size, "%chat_output%", reply->chat_output))
			continue;
		if (0 != str_replace(dst, size, "%chat_status%", reply->chat_status))
			continue;
		if (0 != str_replace(dst, size, "%chat_channel%", reply->chat_channel))
			continue;
		if (0 != str_replace(dst, size, "%chat_sender%", reply->chat_sender))
			continue;
		if (0 != str_replace(dst, size, "%chat_color%", color))
			continue;
		if (0 != str_replace(dst, size, "%chat_payload%", reply->chat_payload))
			continue;

		if (0 != str_replace(dst, size, "%loadgraphics%", img_loadgraphics))
			continue;
		if (0 != str_replace(dst, size, "%latestbuild%", img_latestbuild))
			continue;

		/* replaced them all, so we can break out */
		break;
	}

	len = strlen(dst);
	LOGS(L_PROXY,L_DEBUG,("size: %#x length: %#x\n",
		(unsigned)size, (unsigned)len));

	reply->user = dst;
	reply->content_length = len;

	xfree(*pstr);
	*pstr = dst;
}

/*
 *	url_encode()
 *	Decode an escaped (URL encoded) string
 */
int url_encode(char **purl, const char *url, int blanks)
{
	size_t linesize = strlen(url) + 16, offs;
	char *line = xcalloc(linesize, sizeof(char));
	FUN("url_encode");

	for (offs = 0; *url; url++) {
		if (offs >= linesize - 8) {
			linesize *= 2;
			line = xrealloc(line, linesize);
		}
		switch (*url) {
		case ' ':
			line[offs++] = (0 == blanks) ? ' ' : '+';
			break;
		case '%': case '+': case '?': case '"':
		case '&': case '<': case '=': case '>':
			offs += sprintf(&line[offs], "%%%X", (uint8_t)*url);
			break;
		default:
			line[offs++] = *url;
			break;
		}
	}
	line[offs] = '\0';
	*purl = line;
	return 0;
}

/*
 *	url_decode()
 *	Decode an escaped (URL encoded) string
 */
int url_decode(char **purl, const char *url)
{
	size_t linesize = LINESIZE, offs;
	char *line = xcalloc(linesize, sizeof(char));
	FUN("url_decode");

	offs = 0;
	while (*url) {
		/* reallocate buffer if we're going to reach the end soon */
		if (offs >= linesize - 4) {
			linesize *= 2;
			line = xrealloc(line, linesize);
		}
		switch (*url) {
		case '+':
			line[offs++] = ' ';
			url++;
			break;
		case '%':
			if (url[1] == '%') {
				line[offs++] = '%';
				url += 2;
			} else if (isxdigit(url[1]) && isxdigit(url[2])) {
				line[offs++] = digit(url[1]) * 16 + digit(url[2]);
				url += 3;
			} else {
				line[offs++] = *url++;
			}
			break;
		case '&':
			if (0 == memcmp(url,"&quot;",6)) {
				line[offs++] = '"';
				url += 6;
			} else if (0 == memcmp(url,"&amp;",5)) {
				line[offs++] = '&';
				url += 5;
			} else if (0 == memcmp(url,"&lt;",4)) {
				line[offs++] = '<';
				url += 4;
			} else if (0 == memcmp(url,"&gt;",4)) {
				line[offs++] = '>';
				url += 4;
			} else {
				line[offs++] = *url;
				url++;
			}
			break;
		default:
			line[offs++] = *url;
			url++;
			break;
		}
	}
	line[offs] = '\0';
	*purl = line;
	return 0;
}

/*
 *	url_parse()
 *	Parse the key=value pairs in an URL into two arrays of strings.
 *	The maximum number of arguments is given in 'maxargs'. The caller
 *	must [x]free() the strings afterwards.
 */
static int url_parse(char **keys, char **vals, int maxargs, char *url)
{
	char *tmp = NULL;
	char *dst;
	int n = 0, arg = 0, valarg = 0, rc = 0;
	FUN("url_parse");

	for (n = maxargs; n > 0; --n)
		keys[n] = vals[n] = NULL;
	tmp = xcalloc(strlen(url), sizeof(char));
	dst = url;

	while (*url) {
		switch (*url) {
		case '?':
			*url = '\0';	/* terminate the URL here */
			*dst = '\0';
			if (2 == valarg) {
				LOGS(L_PROXY,L_MINOR,("found val #%d: '%s'\n", n, tmp));
				if (n < maxargs)
					url_decode(&vals[n], tmp);
				n++;
			}
			dst = tmp;
			valarg = 1;
			arg = 1;
			break;

		case '&':
			if (0 == arg) {
				*dst++ = *url;
				break;
			}
			*dst = '\0';
			if (2 == valarg) {
				LOGS(L_PROXY,L_MINOR,("found val #%d: '%s'\n", n, tmp));
				if (n < maxargs)
					url_decode(&vals[n], tmp);
				n++;
			}
			dst = tmp;
			valarg = 1;
			break;

		case '=':
			if (0 == arg) {
				*dst++ = *url;
				break;
			}
			*dst = '\0';
			if (1 == valarg) {
				LOGS(L_PROXY,L_MINOR,("found key #%d: '%s'\n", n, tmp));
				if (n < maxargs)
					url_decode(&keys[n], tmp);
			}
			dst = tmp;
			valarg = 2;
			break;

		default:
			LOGS(L_PROXY,L_DEBUGX,("'%c'\n", *url));
			*dst++ = *url;
		}
		url++;
	}

	/* is there a last value? */
	if (2 == valarg) {
		*dst = '\0';
		LOGS(L_PROXY,L_MINOR,("found val #%d: '%s'\n", n, tmp));
		if (n < maxargs)
			url_decode(&vals[n], tmp);
		n++;
	}

	xfree(tmp);
	return rc;
}

static int proxy_html_filter(tempfile_t *tfi)
{
	static const char base64[] =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-,~";
	tempfile_t *tfo = NULL;
	char *name = NULL;
	char *buff = NULL;
	protocol_t *proto = NULL;
	size_t size;
	size_t offs;
	size_t scan;
	size_t last;
	size_t done;
	size_t i;
	int html;
	int cmnt;
	int rc = 0;
	FUN("proxy_html_filter");

	if (tfi->size <= 0) {
		LOGS(L_PROXY,L_ERROR,("document size is <= 0\n"));
		return -1;
	}

	name = xstrdup(tfi->name);

	buff = xcalloc(tfi->size + 1, sizeof(char));
	rc = temp_read(tfi, buff, tfi->size, &done);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("reading %#x bytes failed (%s)\n",
			(unsigned)tfi->size, strerror(errno)));
		goto bailout;
	}
	if (done != tfi->size) {
		LOGS(L_PROXY,L_ERROR,("got only %#x of %#x bytes (%s)\n",
			(unsigned)done, (unsigned)tfi->size, strerror(errno)));
		goto bailout;
	}

	tfo = (tempfile_t *)xcalloc(1, sizeof(tempfile_t));
	rc = temp_closed(tfo, name, TEMP_UNKNOWN_SIZE);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("creating '%s' failed (%s)\n",
			name, strerror(errno)));
		goto bailout;
	}
	xfree(name);

	rc = temp_open(tfo);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') failed (%s)\n",
			tfo->name, strerror(errno)));
		goto bailout;
	}

	for (offs = 0, last = 0; offs < done; offs++) {
		if (0 == strncasecmp(&buff[offs], "</html>", 7)) {
			offs += 7;
			/* accept trailing tabs, cr, lf and blanks */
			while (offs < done) {
				if (buff[offs] != '\t' &&
					buff[offs] != '\r' &&
					buff[offs] != '\n' &&
					buff[offs] != ' ') break;
				offs++;
			}
			if (done > offs) {
				LOGS(L_PROXY,L_MINOR,("chop doc after trailing </html> @%#x\n" \
					" %-50.50s\n",
					(unsigned)offs, &buff[offs]));
			}
			/* terminate document here and break out */
			done = offs;
			break;
		}

		/* scan for all handled protocols */
		for (i = 0, proto = NULL; i < NUM_PROTOCOLS; i++) {
			proto = &filter_protocol[i];
			if (0 == strncasecmp(&buff[offs], proto->proto, proto->proto_len))
				break;
		}
		if (i == NUM_PROTOCOLS || NULL == proto)
			continue;

		if (offs > last) {
			/* write the previous piece of document from last to offs - 1 */
			rc = temp_write(tfo, &buff[last], offs - last);
			if (0 != rc) {
				LOGS(L_PROXY,L_ERROR,("writing %#x bytes failed (%s)\n",
					(unsigned)(offs - last), strerror(errno)));
				goto bailout;
			}
		}

		/* next piece starts here */
		last = offs;

		/* search backwards for a previous '&' or '<' or '>' */
		for (scan = offs; scan > 0; /* */) {
			--scan;
			if (buff[scan] == '&' || buff[scan] == '>' || buff[scan] == '<')
				break;
		}

		/* check if we're inside a HTML tag; a '>' means we're outside */
		if (buff[scan] == '<' || 0 == strncmp(&buff[scan], "&lt;", 4)) {
			/*
			 * Yes, we are inside a tag, so this document isn't "clean"
			 * Replace ftp:// with /__CHECKED_FTP__
			 * Replace gopher:// with /__CHECKED_GOPHER__
			 * Replace http:// with /__CHECKED_HTTP__
			 * Replace https:// with /__CHECKED_HTTPS__
			 * Replace telnet:// with /__CHECKED_TELNET__
			 * Replace rtsp:// with /__CHECKED_RTSP__
			 */
			rc = temp_write(tfo, "/__CHECKED_", 11);
			rc = temp_write(tfo, proto->name, proto->name_len);
			rc = temp_write(tfo, "__", 2);

			/* skip protocol leadin */
			last += proto->proto_len;

			LOGS(L_PROXY,L_MINOR,("replaced %s reference\n" \
				" document has: %-50.50s\n" \
				" replaced %s by /__CHECKED_%s__\n",
				proto->name, &buff[scan], proto->proto, proto->name));
			/* continue scanning after protocol leadin */
			offs = last - 1;
		}
	}

	if (done > last) {
		/* write the previous piece of document from last to offs - 1 */
		rc = temp_write(tfo, &buff[last], done - last);
		if (0 != rc) {
			LOGS(L_PROXY,L_ERROR,("writing %#x bytes failed (%s)\n",
				(unsigned)(done - last), strerror(errno)));
			goto bailout;
		}
	}
	xfree(buff);

	/* seek back to the beginning of the temp file */
	rc = temp_seek(tfo, 0, SEEK_SET);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("seeking to 0 failed (%s)\n",
			strerror(errno)));
		goto bailout;
	}

	/* second round through, this time scan in memory */
	buff = xcalloc(tfo->size + 1, sizeof(char));
	rc = temp_read(tfo, buff, tfo->size, &done);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("reading %#x bytes failed (%s)\n",
			(unsigned)tfo->size, strerror(errno)));
		goto bailout;
	}
	if (done != tfo->size) {
		LOGS(L_PROXY,L_ERROR,("got only %#x of %#x bytes (%s)\n",
			(unsigned)done, (unsigned)tfo->size, strerror(errno)));
		goto bailout;
	}

	/* seek back to the beginning of the temp file */
	rc = temp_seek(tfo, 0, SEEK_SET);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("seeking to 0 failed (%s)\n",
			strerror(errno)));
		goto bailout;
	}

	/* skip over initial spaces and comments (includes tab, space, cr, lf) */
	for (scan = 0, cmnt = 0; scan < done; scan++) {
		if (0 == cmnt && 0 == strncmp(&buff[scan], "<!--", 4)) {
			cmnt = 1;
			scan += 3;
		} else if (1 == cmnt && 0 == strncmp(&buff[scan], "-->", 3)) {
			cmnt = 0;
			scan += 2;
		} else if (0 == cmnt) {
			/* break on non-white space character */
			if (buff[scan] != '\t' &&
				buff[scan] != '\r' &&
				buff[scan] != '\n' &&
				buff[scan] != ' ') break;
		}
	}
	if (scan >= done) {
		LOGS(L_PROXY,L_ERROR,("skipped initial blanks + newlines to end %#x\n",
			(unsigned)scan));
		rc = -1;
		goto failure;
	}
	/* check for acceptable types of leadins to HTML documents */
	if (0 != strncasecmp(&buff[scan], "<html", 5) &&
		0 != strncasecmp(&buff[scan], "<?xml version", 13) &&
		0 != strncasecmp(&buff[scan], "<!DOCTYPE html", 14)) {
		LOGS(L_PROXY,L_ERROR,("document does not start with valid HTML (%u)\n" \
			"%-50.50s\n", (unsigned)scan, buff));
		rc = -1;
		goto failure;
	}

	/* seek for suspicious double slashes not following a key */
	for (offs = 0, last = 0, html = 0; offs < done; offs++) {
		/* cut comment blocks out of the HTML code */
		if (0 == strncmp(&buff[offs], "<!--", 4)) {
			for (scan = offs + 4; scan < done; scan++)
				if (0 == strncmp(&buff[scan], "-->", 3))
					break;
			/* no end of comment found */
			if (scan >= done) {
				LOGS(L_PROXY,L_ERROR,("document contains open comment\n" \
					"%-50.50s\n", &buff[offs]));
				rc = -1;
				goto failure;
			}
			/* move down scan + 4 to offs */
			scan += 4;
			size = done + 1 - scan;
			memmove(&buff[offs], &buff[scan], size);
			/* subtract size of removed block from done */
			size = scan - offs;
			done -= size;
			/* search again starting at offs */
			offs -= 1;
			continue;
		}
		/* scan until leading <html> tag */
		if (0 == html) {
			if (0 == strncasecmp(&buff[offs], "<html", 5))
				html = 1;
			continue;
		}
		if (0 != strncmp(&buff[offs], "//", 2))
			continue;

		last = offs;
		/* search backwards for a previous '<' or '>' */
		for (scan = offs; scan > 0; /* */) {
			--scan;
			if (buff[scan] == '>' || buff[scan] == '<')
				break;
		}

		/* There's no reason to inspect <meta ...> tags in the <head> */
		if (0 == strncasecmp(&buff[scan], "<meta", 5))
			continue;

		/* check if we're inside a HTML tag; a '>' means we're outside */
		if (buff[scan] == '<') {
			/*
			 * Yes, we are inside a tag, and there should be a XXX@ key
			 * prior to the double slash, or this document isn't "clean"
			 */
			for (offs = scan; offs < last; offs++)
				if (0 == strncasecmp(&buff[offs], "CHK@", 4) ||
					0 == strncasecmp(&buff[offs], "KSK@", 4) ||
					0 == strncasecmp(&buff[offs], "SSK@", 4))
					break;
			if (offs >= last) {
				LOGS(L_PROXY,L_ERROR,("found suspicious double slash\n" \
					" %-50.50s\n", &buff[scan]));
				rc = -1;
			} else {
				/*
				 * only characters from base64 are allowed to occur after
				 * the SSK@ up to the next slash, anything else is suspicious.
				 */
				for (offs += 4; offs < last; offs++) {
					if (buff[offs] == '/')
						break;
					if (NULL == strchr(base64, buff[offs]))
						break;
				}
				/* there was a illegal character if we're not at the slash */
				if (buff[offs] != '/') {
					LOGS(L_PROXY,L_ERROR,("found suspicious double slash\n" \
						" %-50.50s\n", &buff[scan]));
					rc = -1;
				}
			}
			/* continue search after the // */
			offs = last;
		}
	}

failure:
	temp_close(tfi, 1);
	*tfi = *tfo;
	xfree(tfo);
	xfree(name);
	xfree(buff);
	return rc;

bailout:
	if (NULL != tfo)
		temp_close(tfo, 1);
	xfree(tfo);
	xfree(name);
	xfree(buff);
	return -1;
}

/*
 *	pass_tf()
 *	Pass through the contents of a tempfile_t at *user to the
 *	connection given in 'conn'.
 */
static int pass_tf(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	uint8_t *buff = NULL;
	tempfile_t *tf = (tempfile_t *)reply->user;
	size_t done, fcp_blocksize;
	int rc;
	FUN("pass_tf");

	fcp_blocksize = (conn->maxsize < FCP_BLOCKSIZE) ?
		conn->maxsize : FCP_BLOCKSIZE;

	buff = xmalloc(fcp_blocksize);
	for (;;) {
		if (0 != (rc = temp_read(tf, buff, fcp_blocksize, &done)))
			break;
		if (done <= 0)
			break;
		if (0 != (rc = sock_writeall(conn, buff, done)))
			break;
	}

	xfree(buff);
	return 0;
}

/*
 *	pass_fd()
 *	Pass through the contents of a file handle at *(int *)user to the
 *	connection given in 'conn'.
 */
static int pass_fd(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	uint8_t *buff = NULL;
	int fd = *(int *)reply->user;
	size_t done, fcp_blocksize;
	int rc;
	FUN("pass_fd");

	fcp_blocksize = (conn->maxsize < FCP_BLOCKSIZE) ?
		conn->maxsize : FCP_BLOCKSIZE;

	buff = xmalloc(fcp_blocksize);
	for (;;) {
		if (-1 == (int)(done = read(fd, buff, fcp_blocksize)))
			break;
		if (done <= 0)
			break;
		if (0 != (rc = sock_writeall(conn, buff, done))) {
			break;
		}
	}

	xfree(buff);
	return 0;
}

/*
 *	pass_str()
 *	Pass through a string at char *user to the
 *	connection given in 'conn'.
 */
static int pass_str(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;

	return sock_writeall(conn, reply->user, reply->content_length);
}

/*
 *	proxy_reply()
 *	Send a reply to the browser. The supported reply types are 200, 400,
 *	403, 404, 502 and 503. The body of the reply is delivered by a
 *	passthru function which will be called with the given 'conn' argument
 *	and a user defined void * 'user'.
 */
static int proxy_reply(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	void *gzbuff = NULL;
	char *line = NULL;
	char *http_head = NULL;
	char *proxy_version;
	const char *cont;
	size_t gzsize, length;
	time_t time_date = 0, time_modified = 0;
	int rc = 0;
	FUN("proxy_reply");

	line = xcalloc(LINESIZE, sizeof(char));
	http_head = xcalloc(LINESIZE, sizeof(char));

	switch (reply->result_code) {
	case 200:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("OK"));
		break;
	case 301:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Moved permanently"));
		break;
	case 400:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Invalid header received from browser"));
		break;
	case 403:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Access denied"));
		break;
	case 404:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Document not found"));
		break;
	case 502:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Invalid header received from server"));
		break;
	case 503:
		pm_snprintf(http_head, LINESIZE, "HTTP/1.1 %d %s",
			reply->result_code, LO("Connect failed"));
		break;
	}
	proxy_version = NODE_NAME " " PACKAGE_VERSION " internal proxy";
	time_date = time_modified = time(NULL);

	LOGS(L_PROXY,L_MINOR,("-> %s\n", http_head));
	if (0 != (rc = sock_printf(conn,"%s\n", http_head))) {
		goto bailout;
	}
	LOGS(L_PROXY,L_MINOR,("-> Server: %s\n", proxy_version));
	if (0 != (rc = sock_printf(conn,"Server: %s\n", proxy_version))) {
		goto bailout;
	}
	if (reply->result_code == 301 && NULL != reply->location) {
		LOGS(L_PROXY,L_MINOR,("-> Location: %s\n", reply->location));
		rc = sock_printf(conn,"Location: %s\n", reply->location);
		if (0 != rc) {
			goto bailout;
		}
	}
	if (0 != reply->no_cache) {
		LOGS(L_PROXY,L_MINOR,("-> Cache-control: %s\n", "no-cache"));
		rc = sock_printf(conn,"Cache-control: %s\n", "no-cache");
		if (0 != rc) {
			goto bailout;
		}
		rc = sock_printf(conn,"Pragma: %s\n", "no-cache");
		if (0 != rc) {
			goto bailout;
		}
	}

	strftime(line, LINESIZE, "%a, %d %b %Y %H:%M:%S GMT",
		gmtime(&time_date));
	LOGS(L_PROXY,L_MINOR,("-> Date: %s\n", line));
	rc = sock_printf(conn,"Date: %s\n", line);
	if (0 != rc) {
		goto bailout;
	}

	strftime(line, LINESIZE, "%a, %d %b %Y %H:%M:%S GMT",
		gmtime(&time_modified));
	LOGS(L_PROXY,L_MINOR,("-> Last-modified: %s\n", line));
	rc = sock_printf(conn,"Last-modified: %s\n", line);
	if (0 != rc) {
		goto bailout;
	}

	if (NULL != reply->content_type) {
		cont = reply->content_type;
	} else {
		cont = "application/octet-stream";
	}

	LOGS(L_PROXY,L_MINOR,("-> Content-type: %s\n", cont));
	if (0 != (rc = sock_printf(conn,"Content-type: %s\n", cont))) {
		goto bailout;
	}

	if (reply->passthru == pass_str &&
		0 == strncmp(reply->content_type, "text/", 5)) {

		if (1 == reply->accept_encoding) {
			rc = gzip(&gzbuff, &gzsize, reply->user, reply->content_length);

			if (0 == rc) {
				LOGS(L_PROXY,L_MINOR,("-> Vary: Accept-Encoding\n"));
				rc = sock_printf(conn,"Vary: Accept-Encoding\n");
				if (0 != rc) {
					goto bailout;
				}

				LOGS(L_PROXY,L_MINOR,("-> Content-encoding: gzip\n"));
				rc = sock_printf(conn,"Content-encoding: gzip\n");
				if (0 != rc) {
					goto bailout;
				}

				/* subtract header length (4 bytes) artifact */
				length = gzsize - 4;
				LOGS(L_PROXY,L_MINOR,("-> Content-length: %d\n", (int)length));
				rc = sock_printf(conn,"Content-length: %d\n", length);
				if (0 != rc) {
					goto bailout;
				}
				LOGS(L_PROXY,L_MINOR,("->\\n\n"));
				if (0 != (rc = sock_printf(conn,"\n"))) {
					goto bailout;
				}

				LOGS(L_PROXY,L_MINOR,("->[gzdata]\n"));
				rc = sock_writeall(conn, (char *)gzbuff + 4, length);
				goto bailout;
			}
		}
	}

	if (UNKNOWN_LENGTH != reply->content_length) {
		LOGS(L_PROXY,L_MINOR,("-> Content-length: %d\n",
			(int)reply->content_length));
		rc = sock_printf(conn,"Content-length: %d\n",
			(int)reply->content_length);
		if (0 != rc) {
			goto bailout;
		}
	}

	LOGS(L_PROXY,L_MINOR,("->\\n\n"));
	if (0 != (rc = sock_printf(conn,"\n"))) {
		goto bailout;
	}
	
	if (HTTP_HEAD != reply->request_type && NULL != reply->passthru) {
		LOGS(L_PROXY,L_MINOR,("->[data]\n"));
		rc = (*reply->passthru)(conn);
	}

bailout:
	xfree(gzbuff);
	xfree(http_head);
	xfree(line);
	return rc;
}

/*
 *	This is ugly, but is a) required and b) useful, too:
 *	a) not every compiler understands vararg macro definitions (GCC does)
 *	b) formatting error messages requires some discipline :-)
 */

#define	OUT1(fmt) \
	pm_append(&dst,&msg,&max,fmt)
#define	OUT2(fmt,a1) \
	pm_append(&dst,&msg,&max,fmt,a1)
#define	OUT3(fmt,a1,a2) \
	pm_append(&dst,&msg,&max,fmt,a1,a2)
#define	OUT4(fmt,a1,a2,a3) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3)
#define	OUT5(fmt,a1,a2,a3,a4) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3,a4)
#define	OUT6(fmt,a1,a2,a3,a4,a5) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3,a4,a5)
#define	OUT7(fmt,a1,a2,a3,a4,a5,a6) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3,a4,a5,a6)
#define	OUT8(fmt,a1,a2,a3,a4,a5,a6,a7) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3,a4,a5,a6,a7)
#define	OUT9(fmt,a1,a2,a3,a4,a5,a6,a7,a8) \
	pm_append(&dst,&msg,&max,fmt,a1,a2,a3,a4,a5,a6,a7,a8)

static const char *nav(proxy_reply_t *reply, const char *class, const char *fmt, ...)
{
#undef	BUF_NUM
#define	BUF_NUM	16
	static char *buff[BUF_NUM] = {
		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
	};
	static int which = 0;
	va_list ap;
	char *dst, *msg;
	size_t max = 2048;

	which = (which + 1) % BUF_NUM;
	xfree(buff[which]);
	dst = msg = xcalloc(max, sizeof(char));
	if (NULL != class) {
		OUT1("<a name=\"top\">");
	}
	OUT1("<table width=\"100%%\" cellspacing=\"0\" cellpadding=\"2\" border=\"0\" style=\"margin-top:2px; margin-bottom:2px;\">");
	OUT1("<tr class=\"menue\">");
	OUT2("<td>%s</td>",
		LO("Node load"));
	OUT1("<td>");
	OUT2("<a href=\"/index.html\" target=\"_top\">%s</a></td>",
		LO("Gateway"));
	OUT1("<td>");
	OUT2("<a href=\"/chat\" target=\"_top\">%s</a></td>",
		LO("Chat"));
	OUT1("<td>");
	OUT2("<a href=\"/news\" target=\"_top\">%s</a></td>",
		LO("News"));
	OUT1("<td>");
	OUT2("<a href=\"/node/keypair.html\" target=\"_top\">%s</a></td>",
		LO("Keypair"));
	OUT1("<td>");
	OUT2("<a href=\"/node/config.html\" target=\"_top\">%s</a></td>",
		LO("Config"));
	OUT1("<td>");
	OUT2("<a href=\"/node/process.html\" target=\"_top\">%s</a></td>",
		LO("Process"));
	OUT1("<td>");
	OUT2("<a href=\"/node/peers.html\" target=\"_top\">%s</a></td>",
		LO("Peers"));
	OUT1("<td>");
	OUT2("<a href=\"/node/store.html\" target=\"_top\">%s</a></td>\n",
		LO("Store"));
	OUT2("<td>%s</td>",
		LO("Latest build"));
	OUT1("</tr>");
	OUT1("<tr class=\"menue\">");
	OUT1("<td>%%loadgraphics%%</td>");
	if (NULL != class) {
		OUT2("<td class=\"body\" colspan=\"8\"><div class=\"%s\" style=\"border:2px solid #ffffff;\">", class);
		va_start(ap, fmt);
		dst += pm_vsnprintf(dst, max - (size_t)(dst - msg), fmt, ap);
		va_end(ap);
		OUT1("</div></td>");
	} else {
		OUT2("<td class=\"%s\" colspan=\"8\">&nbsp;</td>", class);
	}
	OUT1("<td>%%latestbuild%%</td>");
	OUT1("</tr>");
	OUT1("</table>\n");
	if (NULL != class) {
		OUT1("</a>");
	}
	buff[which] = msg;
	return msg;
}

/*
 *	proxy_301()
 *	Send a '301 Moved permanently' reply to the connection in 'conn'
 */
static int proxy_301(conn_t *conn, const char *uri)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc;
	FUN("proxy_301");

	reply->result_code = 301;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Warning"), reply->result_code,
		LO("Moved permanently"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "warning", "%s %d: %s",
			LO("Warning"),
			reply->result_code,
			LO("Moved permanently")));
	OUT6("  <h4>%s <a href=\"%s\">%s</a> %s %s</h4>\n",
		LO("You will be redirected to"),
		reply->location, reply->location,
		LO("from"), uri);
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_400()
 *	Send a '400 Invalid header received from browser' reply
 *	to the connection in 'conn'
 */
static int proxy_400(conn_t *conn, const char *error)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc;
	FUN("proxy_400");

	reply->result_code = 400;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Error"), reply->result_code,
		LO("Invalid header received from browser"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "error", "%s %d: %s",
			LO("Error"),
			reply->result_code,
			LO("Invalid header received from browser")));
	OUT2("  <h4>%s</h4>\n",
		LO("Something is seriously wrong here..."));
	OUT2("  <p>%s</p>\n",
		error);
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_403()
 *	Send a '403 Access denied' reply
 *	to the connection in 'conn'
 */
static int proxy_403(conn_t *conn, const char *uri)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc;
	FUN("proxy_403");

	reply->result_code = 403;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Error"), reply->result_code,
		LO("Access denied"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "error", "%s %d: %s",
			LO("Error"),
			reply->result_code,
			LO("Access denied")));
	OUT3("  <h4>%s %s</h4>\n",
		LO("You do not have sufficient rights to access"),
		uri);
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);
	xfree(msg);
	return rc;
}

/*
 *	proxy_404()
 *	Send a '404 Document Not Found' reply to the connection in 'conn'
 */
static int proxy_404(conn_t *conn, const char *url, const char *uri)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int timeout, rc;
	FUN("proxy_404");

	reply->result_code = 404;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	switch (reply->retry_number) {
	case 0: case 1:
		timeout = 15;
		break;
	case 2:
		timeout = 30;
		break;
	case 3:
		timeout = 45;
		break;
	default:
		timeout = 60;
	}

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Error"), reply->result_code,
		LO("Document not found"));
	if (reply->retry_number >= 0) {
		OUT4("  <meta http-equiv=\"refresh\" content=\"%d; URL=/%s?try=%d\">\n",
			timeout, uri, reply->retry_number + 1);
	}
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "error", "%s: %d %s",
			LO("Error"),
			reply->result_code,
			LO("Document not found")));
	OUT2("  <h4>%s</h4>\n",
		url);
	if (reply->retry_number >= 0) {
		OUT2("  <p>%s</p>\n",
			LO("You can try to reload this page."));
		OUT7("  <p>%s %s %s <img src=\"/node/s%d.gif\" width=\"17\" height=\"12\" border=\"1\" hspace=\"1\" align=\"bottom\" alt=\"%d\"/> %s</p>\n",
			LO("If you don't do anything now,"),
			NODE_NAME,
			LO("will retry to load the URL again after"),
			timeout, timeout, LO("seconds."));
	} else {
		OUT2("  <p>%s</p>\n",
			LO("You should check the link where you came from."));
	}
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_502()
 *	Send a '502 Invalid header received from server' reply to
 *	the connection 'conn'
 */
static int proxy_502(conn_t *conn, const char *uri)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc;
	FUN("proxy_502");

	reply->result_code = 502;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Error"), reply->result_code,
		LO("Invalid header received from server"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "error", "%s %d: %s",
			LO("Error"),
			reply->result_code,
			LO("Invalid header received from server")));
	OUT2("  <h4>%s</h4>\n",
		uri);
	OUT2("  <p>%s</p>\n",
		LO("This is no good sign, since it means that either the data is corrupt or the node is behaving in an unexpected way."));
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_503()
 *	Send a '503 Connect failed' reply
 *	to the connection in 'conn'
 */
static int proxy_503(conn_t *conn, const char *uri)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc;
	FUN("proxy_503");

	reply->result_code = 503;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>" NODE_NAME " %s: %d %s</title>\n",
		LO("Error"), reply->result_code,
		LO("Connect failed"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "error", "%s %d: %s",
			LO("Error"),
			reply->result_code,
			LO("Connect failed")));
	OUT2("  <h4>%s</h4>\n",
		uri);
	OUT3("  <p>%s %s</p>\n",
		NODE_NAME,
		LO("was not able to connect."));
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/* callback for informational messages during file_get() */
static int get_cb(void *user, file_info_t *info)
{
	conn_t *conn = (conn_t *)user;
	fd_set rdfds, wrfds, exfds;
	struct timeval tv;
	int sk;
	FUN("get_cb");

	if (INFO_TYPE_RESTARTED == info->type) {
		LOGS(L_PROXY,L_MINOR,("restarted SHA1=%s\n",
			sha1_hexstr(&info->hash)));
	} else {
		LOGS(L_PROXY,L_MINOR,("callback SHA1=%s %s/%s\n",
			sha1_hexstr(&info->hash),
			round_KMG(info->offs),
			round_KMG(info->size)));
	}
	sk = conn->socket;
	FD_ZERO(&rdfds);
	FD_SET(sk, &rdfds);
	FD_ZERO(&wrfds);
	FD_SET(sk, &wrfds);
	FD_ZERO(&exfds);
	FD_SET(sk, &exfds);
	memset(&tv, 0, sizeof(tv));
	if (select(sk + 1, &rdfds, &wrfds, &exfds, &tv) < 0) {
		LOGS(L_PROXY,L_DEBUG,("select(%d,...) failed (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	if (FD_ISSET(sk, &exfds)) {
		LOGS(L_PROXY,L_DEBUG,("sk %d exception (%s)\n",
			sk, strerror(errno)));
		return -1;
	}
	return 0;
}

static int proxy_splitfile(conn_t *conn, tempfile_t *tfd, tempfile_t *tfm, char *uri)
{
	tempfile_t tfdata, tfmeta;
	proxy_reply_t *reply = conn->temp;
	chkey_t key;
	uint8_t *buff = NULL;
	int rc = 0;
	FUN("proxy_splitfile");

	temp_invalid(&tfdata);
	temp_invalid(&tfmeta);
	buff = xmalloc(LINESIZE);

	if (0 != (rc = is_valid_chk(&key, uri))) {
		LOGS(L_PROXY,L_ERROR,("'%s' is no valid URI!\n", uri));
		rc = -EIO;
		goto bailout;
	}

	if (0 != (rc = temp_closed(&tfdata, "data", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) call failed (%s)\n",
			tfdata.name, strerror(errno)));
		goto bailout;
	}
	if (0 != (rc = temp_closed(&tfmeta, "meta", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) call failed (%s)\n",
			tfmeta.name, strerror(errno)));
		goto bailout;
	}

	rc = file_get(&key, &tfdata, &tfmeta, reply->htl, NULL, conn, get_cb, 0);
	if (0 != rc) {
		LOGS(L_PROXY,L_ERROR,("file_get(%s) call failed!\n",
			key_str(&key)));
		goto bailout;
	}

	if (0 != (rc = temp_open(&tfdata))) {
		LOGS(L_PROXY,L_MINOR,("temp_open('%s') call failed (%s)\n",
			tfdata.name, strerror(errno)));
		goto bailout;
	}

	/* copy data */
	for (;;) {
		size_t done;

		if (0 != (rc = temp_read(&tfdata, buff, LINESIZE, &done))) {
			LOGS(L_PROXY,L_ERROR,("temp_read(%s,%#x) call failed (%s)\n",
				tfdata.name, LINESIZE, strerror(errno)));
			break;
		}
		if (0 == done)
			break;
		if (0 != (rc = temp_write(tfd, buff, done))) {
			LOGS(L_PROXY,L_ERROR,("temp_write(%s,%#x) call failed (%s)\n",
				tfd->name, done, strerror(errno)));
			break;
		}
	}

	if (0 != (rc = temp_open(&tfmeta))) {
		LOGS(L_PROXY,L_MINOR,("temp_open('%s') call failed (%s)\n",
			tfmeta.name, strerror(errno)));
		/* no (valid) meta data is not an error */
		rc = 0;
		goto bailout;
	}

	/* nothing to copy */
	if (0 == tfmeta.size)
		goto bailout;

	/* copy meta data */
	for (;;) {
		size_t done;

		if (0 != (rc = temp_read(&tfmeta, buff, LINESIZE, &done))) {
			LOGS(L_PROXY,L_ERROR,("temp_read(%s,%#x) call failed (%s)\n",
				tfmeta.name, LINESIZE, strerror(errno)));
			break;
		}
		if (0 == done)
			break;
		if (0 != (rc = temp_write(tfm, buff, done))) {
			LOGS(L_PROXY,L_ERROR,("temp_write(%s,%#x) call failed (%s)\n",
				tfm->name, done, strerror(errno)));
			break;
		}
	}

bailout:
	xfree(buff);
	temp_close(&tfdata, 1);
	temp_close(&tfmeta, 1);
	return rc;
}

/*
 *	proxy_meta_find()
 *	Find a variable (and optionally a value) in the meta data 'meta'
 */
static void *proxy_meta_find(meta_t *meta, char *var, char *val)
{
	meta_doc_t *doc, *doc0;
	size_t i;
	FUN("proxy_meta_find");

	if (NULL == meta || 0 == meta->count) {
		LOGS(L_PROXY,L_DEBUG,("document is empty\n"));
		return NULL;
	}

	LOGS(L_PROXY,L_DEBUG,("searching %s=%s in %d entries\n",
		var, (NULL == val) ? "[anything]" : val,
		(int)meta->count));
	doc0 = &meta->docs[0];
	for (i = 0; i < meta->count; i++) {
		doc = &meta->docs[i];
		/* new part starts here? */
		if (doc0->idx != doc->idx) {
			doc0 = doc;
		}
		if (0 == strcasecmp(var, doc->var)) {
			if (NULL == val) {
				LOGS(L_PROXY,L_DEBUG,("found in #%d, idx %d\n",
					(int)i, (int)doc->idx));
				return doc->val;
			} else if (0 == strcasecmp(val, doc->val)) {
				LOGS(L_PROXY,L_DEBUG,("found in #%d\n",
					(int)i));
				return doc0;
			}
		}
	}

	return NULL;
}

/*
 *	proxy_meta_default()
 *	Find a default entry in the meta->docs. I love the metadata
 *	syntax .. a document without a "Name=" field is the default :-P
 */
static void *proxy_meta_default(meta_t *meta)
{
	size_t i, j;
	meta_doc_t *doc;
	int default_doc;
	FUN("proxy_meta_default");

	if (NULL == meta || 0 == meta->count) {
		LOGS(L_PROXY,L_DEBUG,("document tree is empty\n"));
		return NULL;
	}

	LOGS(L_PROXY,L_DEBUG,("searching default entry in %d entries\n",
		(int)meta->count));
	if (1 == meta->count) {
		return &meta->docs[0];
	}
	default_doc = 0;
	for (i = 0, j = 0; i < meta->count; i++) {
		doc = &meta->docs[i];
		/* new part starts here? */
		if (meta->docs[j].idx != doc->idx) {
			if (0 != default_doc) {
				LOGS(L_PROXY,L_DEBUG,("*** found: beginning with entry #%d\n",
					(int)j));
				return &meta->docs[j];
			}
			LOGS(L_PROXY,L_DEBUG,("entry #%d has new idx %d\n",
				(int)i, (int)doc->idx));
			j = i;
			default_doc = 1;
		}
		if (0 == strcasecmp(doc->var, "Name")) {
			LOGS(L_PROXY,L_DEBUG,("entry #%d 'Name' -- not default\n",
				(int)i));
			default_doc = 0;
		}
	}
	if (0 != default_doc) {
		return &meta->docs[j];
	}

	return NULL;
}

/*
 *	proxy_meta_cache_get()
 *	Check the proxy cache for a key of a metadata only document.
 */
static int proxy_meta_cache_get(chkey_t *key, meta_t **pmeta)
{
	proxy_cache_t *c;
	meta_t *meta = NULL;
	char *filename = NULL;
	size_t i, size;
	int fd = -1;
	FUN("proxy_meta_cache_get");

	if (0 == g_conf->proxycache) {
		errno = ENOENT;
		return -1;
	}

	if (0 != PROXY_CACHE_LOCK()) {
		LOGS(L_PROXY,L_ERROR,("locking g_proxy failed (%s)\n",
			strerror(errno)));
		return -1;
	}

	for (i = 0; i < g_proxy->size; i++) {
		c = &g_proxy->cache[i];
		if (0 == memcmp(&c->sha1, &key->sha1, SHA1SIZE)) {
			LOGS(L_PROXY,L_MINOR,("cache hit %s slot #%#x/%#x\n",
				sha1_hexshort(&key->sha1),
				(unsigned)i, (unsigned)g_proxy->size));
			c->time = time(NULL);

			/* conventional memory copy of the meta structure */
			size = sizeof(meta_t) + c->meta->count * sizeof(meta_doc_t);
			meta = (meta_t *)xmalloc(size);
			memcpy(meta, c->meta, size);

			/* conventional memory copy of the meta data */
			meta->data = (char *)xcalloc(meta->size + 1, sizeof(char));

			pm_asprintf(&filename, "%s/%s-%s-%s.bin",
				g_conf->temppath, g_conf->progname, "meta",
				sha1_hexstr(&key->sha1));

			fd = open(filename, O_RDONLY|O_BINARY);
			if (-1 == fd) {
				LOGS(L_PROXY,L_MINOR,("open('%s',O_RDONLY) failed (%s)\n",
					filename, strerror(errno)));
				break;
			}
			if (meta->size != (size_t)read(fd, meta->data, meta->size)) {
				LOGS(L_PROXY,L_MINOR,("read('%s',%#x) failed (%s)\n",
					filename, (unsigned)meta->size, strerror(errno)));
				break;
			}
			xfree(filename);
			close(fd);
			fd = -1;

			/* decrypt the buffer with the EK5 hash */
			key_crypt(key->ek5.digest, meta->data, meta->size, 0);

			/* adjust the pointers to the documents to meta->data base */
			for (i = 0; i < meta->count; i++) {
				meta_doc_t *doc = &meta->docs[i];
				doc->var = &meta->data[(unsigned)doc->var];
				if (NULL != doc->val)
					doc->val = &meta->data[(unsigned)doc->val];
			}

			PROXY_CACHE_UNLOCK();
			*pmeta = meta;
			return 0;
		}
	}
	PROXY_CACHE_UNLOCK();
	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	xfree(filename);
	LOGS(L_PROXY,L_MINOR,("cache miss %s\n",
		sha1_hexshort(&key->sha1)));
	errno = ENOENT;
	return -1;
}

#define	DUMMY_METADATA \
	"Version\n" \
	"Revision=1\n" \
	"EndPart\n" \
	"Document\n" \
	"Info.Description=file\n" \
	"End\n"

/*
 *	proxy_meta_parse()
 *	Parse a meta data file and return a list of the documents and the count.
 *	Try to put the parsed meta data into proxy cache if cache_it is non zero.
 */
static int proxy_meta_parse(chkey_t *key, tempfile_t *tfmeta,
	meta_t **pmeta, int cache_it)
{
	proxy_cache_t *c, *c0;
	meta_t *meta = NULL;
	meta_t *shm_meta = NULL;
	meta_t *old_meta = NULL;
	sha1_digest_t old_sha1;
	uint8_t *data = NULL;
	char *filename = NULL;
	char *src, *var, *val;
	size_t i, size, docs, idx;
	int fd = -1;
	int rc = 0;
	FUN("proxy_meta_parse");

	memset(&old_sha1, 0, sizeof(old_sha1));

	if (0 != (rc = temp_open(tfmeta))) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfmeta->name, strerror(errno)));
		return -1;
	}

	size = tfmeta->size;
	if (0 == size) {
		/* supply a dummy meta data block */
		size = strlen(DUMMY_METADATA) + 1;

		LOGS(L_PROXY,L_MINOR,("size of '%s' is zero\n",
			tfmeta->name));

		docs = 2;
		meta = (meta_t *)xcalloc(1, sizeof(meta_t) +
			docs * sizeof(meta_doc_t));
		meta->size = size;
		meta->max = docs;
		meta->count = 0;
		meta->data = xcalloc(size, sizeof(char));
		pm_snprintf(meta->data, size, DUMMY_METADATA);

		LOGS(L_PROXY,L_MINOR,("%u bytes for '%s', %u docs (%u bytes)\n",
			(unsigned)size, tfmeta->name, (unsigned)docs,
			(unsigned)(sizeof(meta_t) + docs * sizeof(meta_doc_t))));
	} else {
		LOGS(L_PROXY,L_MINOR,("size of '%s' is %#x\n",
			tfmeta->name, (unsigned)size));

		/* add room for a trailing NUL char */
		size += 1;

		docs = size / 32 + 2;
		LOGS(L_PROXY,L_MINOR,("%u bytes for '%s', %u docs (%u bytes)\n",
			(unsigned)size, tfmeta->name, (unsigned)docs,
			(unsigned)(sizeof(meta_t) + docs * sizeof(meta_doc_t))));
		meta = (meta_t *)xcalloc(1, sizeof(meta_t) +
			docs * sizeof(meta_doc_t));
		meta->size = size;
		meta->max = docs;
		meta->count = 0;
		meta->data = xcalloc(size, sizeof(char));
		if (0 != (rc = temp_read(tfmeta, meta->data, size, &size))) {
			LOGS(L_PROXY,L_ERROR,("temp_read() from '%s' call failed (%s)\n",
				tfmeta->name, strerror(errno)));
			temp_close(tfmeta, 0);
			return -1;
		}
		temp_close(tfmeta, 0);
	}

	var = meta->data;
	val = NULL;
	idx = 0;
	for (src = meta->data; src < meta->data + meta->size; src++) {
		/* end of line character or \0? */
		if (*src == '\0' || *src == '\r' || *src == '\n') {
			/* end of line or end of text */
			*src = '\0';
			if (NULL != var) {
				/* only store variables other than these 4 */
				if (0 != strcasecmp(var, "Version") &&
					0 != strcasecmp(var, "Document") &&
					0 != strcasecmp(var, "EndPart") &&
					0 != strcasecmp(var, "End")) {
					meta_doc_t *doc = &meta->docs[meta->count];
					LOGS(L_PROXY,L_DEBUG,("meta %3d/%3d: %s%s%s\n",
						(int)meta->count, (int)idx, var,
						NULL != val ? "=" : "",
						NULL != val ? val : ""));
					doc->idx = idx;
					doc->var = var;
					doc->val = val;
					meta->count += 1;
				}
				/* increment document index on End[Part] */
				if (0 == strcasecmp(var, "EndPart") ||
					0 == strcasecmp(var, "End")) {
					idx = meta->count;
				}
				var = NULL;
				val = NULL;
			}
		} else if (val == NULL && *src == '=') {
			/* found the first '=' on this line */
			*src = '\0';
			val = src + 1;
		} else if (var == NULL) {
			/* found the first non CR or LF character */
			var = src;
		}
	}

	if (0 != cache_it && g_conf->proxycache > 0) {
		/* allocate a shared memory copy of the meta structure */
		size = sizeof(meta_t) + meta->count * sizeof(meta_doc_t);
		shm_meta = (meta_t *)smalloc(size);
		if (NULL == shm_meta) {
			LOGS(L_PROXY,L_ERROR,("smalloc(%#x) failed (%s)\n",
				(unsigned)size, strerror(errno)));
			/* no error, because we 'only' have no shm for the cache */
			return 0;
		}
		memcpy(shm_meta, meta, size);

		/* make the pointers to the documents zero based */
		for (i = 0; i < shm_meta->count; i++) {
			meta_doc_t *doc = &shm_meta->docs[i];
			/* ugly pointer magic.. */
			doc->var = (char *)(doc->var - meta->data);
			if (NULL != doc->val)
				doc->val = (char *)(doc->val - meta->data);
		}
		/* write meta data to a temp file */
		pm_asprintf(&filename, "%s/%s-%s-%s.bin",
			g_conf->temppath, g_conf->progname, "meta",
			sha1_hexstr(&key->sha1));

		fd = open(filename, O_TRUNC|O_CREAT|O_RDWR|O_BINARY, 0600);
		if (-1 == fd) {
			LOGS(L_PROXY,L_ERROR,("open('%s',O_TRUNC|O_CREAT...) failed (%s)\n",
				filename, strerror(errno)));
			/* no error, because we 'only' can't write the file */
			return 0;
		}

		/* make an encrypted copy of the parsed data */
		data = (char *)xcalloc(meta->size + 1, sizeof(char));
		memcpy(data, meta->data, meta->size);
		key_crypt(key->ek5.digest, data, meta->size, 0);

		if (meta->size != (size_t)write(fd, data, meta->size)) {
			LOGS(L_PROXY,L_ERROR,("write('%s',%#x) failed (%s)\n",
				filename, (unsigned)meta->size, strerror(errno)));
			/* no error, because we 'only' can't write the file */
			return 0;
		}
		close(fd);
		fd = -1;
		xfree(filename);
		xfree(data);

		if (0 != PROXY_CACHE_LOCK()) {
			LOGS(L_PROXY,L_ERROR,("locking g_proxy failed (%s)\n",
				strerror(errno)));
			return -1;
		}
		/* do we have to overwrite an entry? */
		if (g_proxy->size >= g_conf->proxycache) {
			c = &g_proxy->cache[0];
			/* find oldest entry */
			for (i = 1; i < g_proxy->size; i++) {
				c0 = &g_proxy->cache[i];
				if (c0->time < c->time)
					c = c0;
			}
			/* save old meta structure pointer, so we can free it */
			old_meta = c->meta;
			old_sha1 = c->sha1;
		} else {
			/* append to last entry and increase size */
			c = &g_proxy->cache[g_proxy->size];
			g_proxy->size += 1;
		}
		/* set the cache entry new values */
		c->sha1 = key->sha1;
		c->time = time(NULL);
		c->meta = shm_meta;
		PROXY_CACHE_UNLOCK();
		/* free previous meta data and structure, if any */
		if (NULL != old_meta) {
			/* unlink old temp file */
			pm_asprintf(&filename, "%s/%s-%s-%s.bin",
				g_conf->temppath, g_conf->progname, "meta",
				sha1_hexstr(&old_sha1));
			if (0 != unlink(filename)) {
				LOGS(L_PROXY,L_ERROR,("unlink('%s') failed (%s)\n",
					filename, strerror(errno)));
			}
			sfree((caddr_t)old_meta);
			/* just cosmetics... */
			old_meta = NULL;
			memset(&old_sha1, 0, sizeof(old_sha1));
		}
	}

	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	xfree(filename);
	xfree(data);
	*pmeta = meta;
	return 0;
}

/*
 *	proxy_loop()
 *	Retrieve a freenet contents given in 'uri' an loop it back to
 *	to the browser. This function does all the map space lookup and
 *	split file decomposition prior to replying with the data.
 */
static int proxy_loop(conn_t *conn, const char *uri)
{
	tempfile_t tfdata, tfmeta, *tf;
	proxy_reply_t *reply = conn->temp;
	chkey_t key;
	char *url = NULL;
	char *msk = NULL;
	char *msf = NULL;
	char *block = NULL;
	char *str;
	size_t len, i, n, n0;
	meta_t *meta = NULL;
	meta_doc_t *doc = NULL;
	int rc = 0;
	FUN("proxy_loop");

	len = strlen(uri);
	url = xcalloc(LINESIZE, sizeof(char));
	strncpy(url, uri, LINESIZE);

	temp_invalid(&tfdata);
	temp_invalid(&tfmeta);

	while (url[0] != '\0') {

		/* free previous meta data */
		if (meta) {
			if (meta->data)
				xfree(meta->data);
			xfree(meta);
		}

		if (NULL != msk || NULL != strstr(url,"//")) {

			/* first time parsing into a map space key? */
			if (NULL == msk) {
				/* leave room for eventually appending index.html */
				msk = xstrndup(url, strlen(url) + strlen("index.html") + 1);
				msf = strstr(msk, "//");
				*msf++ = '\0';			/* terminate MSK URL */
				strncpy(url, msk, LINESIZE);
				url[LINESIZE-1] = '\0';
				msf++;	/* msf is the path-/filename part below the MSK */
				len = strlen(msf);
				if (len > 0 && msf[len-1] == '/') {
					/* name ends in a slash: most probably index.html */
					strcat(msf, "index.html");
				}
			}

			LOGS(L_PROXY,L_MINOR,("checking MSK '%s'\n", url));
			if (0 != (rc = is_valid_uri(&key,url))) {
				LOGS(L_PROXY,L_ERROR,("not a valid URI: %s\n", url));
				goto bailout;
			}

			if (key.type[0] == MSB(K_SSK_P) &&
				key.type[1] == LSB(K_SSK_P)) {
				if (0 != (rc = create_chk_from_ksk(&key,url))) {
					LOGS(L_PROXY,L_ERROR,("cannot create CHK from key: %s\n", url));
					goto bailout;
				}
			}
			url[0] = '\0';

			if (0 == reply->meta &&
				0 == proxy_meta_cache_get(&key, &meta)) {
				LOGS(L_PROXY,L_DEBUG,("found %d docs in %u bytes metadata\n",
					(int)meta->count, (unsigned)meta->size));
			} else {
				rc = temp_closed(&tfdata, "data", TEMP_UNKNOWN_SIZE);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) failed (%s)\n",
						tfdata.name, strerror(errno)));
					goto bailout;
				}
				rc = temp_closed(&tfmeta, "meta", TEMP_UNKNOWN_SIZE);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) failed (%s)\n",
						tfmeta.name, strerror(errno)));
					goto bailout;
				}
				rc = file_get(&key, &tfdata, &tfmeta, reply->htl, NULL,
					conn, get_cb, 0);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("file_get(%s) call failed\n",
						key_short(&key)));
					rc = proxy_404(conn, url, uri);
					goto bailout;
				}
				/* parse meta data; cache it if tfdata.size is zero */
				rc = proxy_meta_parse(&key, &tfmeta, &meta,
					(0 == tfdata.size) ? 1 : 0);

				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("proxy_meta_parse() failed (%s)\n",
						strerror(errno)));
					rc = proxy_502(conn,url);
					goto bailout;
				}
				LOGS(L_PROXY,L_DEBUG,("parsed %d docs in %u bytes metadata\n",
					(int)meta->count, (unsigned)meta->size));
			}
			/* This level meta data wanted? */
			if (1 == reply->meta)
				break;

			str = proxy_meta_find(meta, "DateRedirect.Target", NULL);
			if (NULL != str) {
				char *slash;
				time_t t = time(NULL);
				time_t to = 0;				/* default time offset */
				time_t ti = 24 * 60 * 60;	/* default time increment */
				char *str_to, *str_ti;

				LOGS(L_PROXY,L_MINOR,("DateRedirect.Target=%s\n", str));

				if (NULL != (str_to = proxy_meta_find(meta,
					"DateRedirect.Offset", NULL))) {
					to = strtoul(str_to, NULL, 16);
					LOGS(L_PROXY,L_MINOR,("DateRedirect.Offset=%x\n",
						(unsigned)to));
				}
				if (NULL != (str_ti = proxy_meta_find(meta,
					"DateRedirect.Increment", NULL))) {
					ti = strtoul(str_ti, NULL, 16);
					LOGS(L_PROXY,L_MINOR,("DateRedirect.Increment=%x\n",
						(unsigned)ti));
					if (ti <= 0) {
						LOGS(L_PROXY,L_ERROR,("Bad DateRedirect.Increment=%x\n",
							(unsigned)ti));
						ti = 24 * 60 * 60;
					}
				}
				if (NULL == (slash = strchr(str, '/'))) {
					LOGS(L_PROXY,L_ERROR,("DateRedirect has no slash: %s\n",
						str));
					t = t - (t % ti) - to;
					/* shall we try to append the time to the URL? */
					pm_snprintf(url, LINESIZE, "%s-%08x",
						str, (uint32_t)t);
				} else {
					*slash = '\0';
					t = t - (t % ti) - to;
					pm_snprintf(url, LINESIZE, "%s/%08x-%s",
						str, (uint32_t)t, slash + 1);
					*slash = '/';
				}
				LOGS(L_PROXY,L_MINOR,("MSK DateRedirect to %s\n", url));
				if (0 != (rc = is_valid_uri(&key,url))) {
					LOGS(L_PROXY,L_ERROR,("not a valid URI: %s\n", url));
					goto bailout;
				}

			} else {	/* not a date based redirect (edition or one shot) */

				if (0 == strlen(msf)) {
					/* no map space file: use the default document */
					doc = proxy_meta_default(meta);
				} else {
					doc = proxy_meta_find(meta, "Name", msf);
					if (NULL == doc) {
						char *dec;
						/* Try with the url_decoded()d map space name */
						if (0 == url_decode(&dec, msf)) {
							doc = proxy_meta_find(meta, "Name", dec);
						}
						xfree(dec);
					}
					if (NULL == doc) {
						/* Hmm... somehow I must find the doc in the CHK@ */
						doc = proxy_meta_default(meta);
					}
				}
				if (NULL == doc) {
					LOGS(L_PROXY,L_ERROR,("'%s' is not in the map space\n",
						msf));
					reply->retry_number = -1;
					rc = proxy_404(conn, uri, uri);
					goto bailout;
				}
				for (i = 0; i < meta->count && doc[0].idx == doc[i].idx; i++) {
					if (0 == strcasecmp(doc[i].var, "Redirect.Target")) {
						pm_snprintf(url, LINESIZE, "%s", doc[i].val);
						LOGS(L_PROXY,L_MINOR,("MSK redirects to %s\n",
							url));
					} else if (0 == strcasecmp(doc[i].var, "Info.Format")) {
						xfree(reply->content_type);
						reply->content_type = xstrdup(doc[i].val);
						LOGS(L_PROXY,L_MINOR,("Info.Format=%s\n",
							reply->content_type));
					}
				}
				if (0 != (rc = is_valid_uri(&key, url))) {
					LOGS(L_PROXY,L_ERROR,("not a valid URI: %s\n", url));
					goto bailout;
				}
			}

		} else {	/* non map space URI (CHK@ or KSK@) */

			LOGS(L_PROXY,L_MINOR,("checking URL '%s'\n", url));
			if (0 != (rc = is_valid_uri(&key, url))) {
				LOGS(L_PROXY,L_ERROR,("not a valid URI: %s\n", url));
				goto bailout;
			}

			if (key.type[0] == MSB(K_SSK_P) &&
				key.type[1] == LSB(K_SSK_P)) {
				if (0 != (rc = create_chk_from_ksk(&key,url))) {
					LOGS(L_PROXY,L_ERROR,("cannot create CHK from key: %s\n", url));
					goto bailout;
				}
			}
			url[0] = '\0';

			if (0 == reply->meta &&
				0 == proxy_meta_cache_get(&key, &meta)) {
				LOGS(L_PROXY,L_DEBUG,("found %d docs in %u bytes metadata\n",
					(int)meta->count, (unsigned)meta->size));
			} else {
				rc = temp_closed(&tfdata, "data", TEMP_UNKNOWN_SIZE);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) failed (%s)\n",
						tfdata.name, strerror(errno)));
					goto bailout;
				}
				rc = temp_closed(&tfmeta, "meta", TEMP_UNKNOWN_SIZE);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("temp_closed('%s',...) failed (%s)\n",
						tfmeta.name, strerror(errno)));
					goto bailout;
				}
				rc = file_get(&key, &tfdata, &tfmeta, reply->htl, NULL,
					conn, get_cb, 0);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("file_get(%s) call failed\n",
						key_short(&key)));
					rc = proxy_404(conn, url, uri);
					goto bailout;
				}

				rc = proxy_meta_parse(&key, &tfmeta, &meta,
					(0 == tfdata.size) ? 1 : 0);
				if (0 != rc) {
					LOGS(L_PROXY,L_ERROR,("proxy_meta_parse() failed (%s)\n",
						strerror(errno)));
					rc = proxy_502(conn,url);
					goto bailout;
				}
				LOGS(L_PROXY,L_DEBUG,("parsed %d docs in %u bytes metadata\n",
					(int)meta->count, (unsigned)meta->size));
			}
			/* This level meta data wanted? */
			if (1 == reply->meta)
				break;

			str = proxy_meta_find(meta, "Info.Format", NULL);
			if (NULL != str) {
				xfree(reply->content_type);
				reply->content_type = xstrdup(str);
				LOGS(L_PROXY,L_MINOR,("Info.Format=%s\n", reply->content_type));
			}

			str = proxy_meta_find(meta, "Redirect.Target", NULL);
			if (NULL != str) {
				strncpy(url, str, LINESIZE);
				url[LINESIZE-1] = '\0';
				LOGS(L_PROXY,L_DEBUG,("KEY redirects to %s\n", url));
				if (0 != (rc = is_valid_uri(&key,url))) {
					LOGS(L_PROXY,L_ERROR,("not a valid URI: %s\n", url));
					goto bailout;
				}
			}
		}
		/* Decrement if there's a request for a deeper level of meta data */
		if (reply->meta > 1) {
			reply->meta -= 1;
		}
	}

	str = proxy_meta_find(meta, "SplitFile.Size", NULL);
	if (NULL != str) {
		size_t count = 0;

		block = xcalloc(LINESIZE, sizeof(char));
		temp_close(&tfdata, 1);
		temp_close(&tfmeta, 1);
		reply->content_length = strtoul(str, NULL, 16);
		LOGS(L_PROXY,L_MINOR,("decoding SplitFile size %#x\n",
			(unsigned)reply->content_length));
		if (0 != (rc = temp_binary(&tfdata, "splitdata", reply->content_length))) {
			LOGS(L_PROXY,L_ERROR,("temp_binary('%s',%#x) failed (%s)\n",
				tfdata.name, reply->content_length, strerror(errno)));
			goto bailout;
		}
		if (0 != (rc = temp_binary(&tfmeta, "splitmeta", TEMP_UNKNOWN_SIZE))) {
			LOGS(L_PROXY,L_ERROR,("temp_binary('%s',%#x) failed (%s)\n",
				tfmeta.name, TEMP_UNKNOWN_SIZE, strerror(errno)));
			goto bailout;
		}
		str = proxy_meta_find(meta, "SplitFile.BlockCount", NULL);
		if (NULL != str) {
			count = strtoul(str, NULL, 16);
			LOGS(L_PROXY,L_MINOR,("decoding SplitFile with %d blocks\n",
				(int)count));
		} else {
			LOGS(L_PROXY,L_MINOR,("decoding SplitFile unspec. number of blocks\n"));
		}
		str = proxy_meta_find(meta, "SplitFile.AlgoName", NULL);
		n0 = (NULL == str) ? 1 : 0;
		for (n = n0; /* */; n++) {
			if (count > 0 && n >= count + n0)
				break;
			pm_snprintf(block, LINESIZE, "SplitFile.Block.%x", (unsigned)n);
			str = proxy_meta_find(meta, block, NULL);
			if (NULL == str)
				break;
			LOGS(L_PROXY,L_MINOR,("loading SplitFile block #%x (%s)\n",
				(unsigned)n, str));
			if (0 != (rc = proxy_splitfile(conn, &tfdata, &tfmeta, str))) {
				LOGS(L_PROXY,L_ERROR,("proxy_splitfile(%s=%s) failed (%s)\n",
					block, str, strerror(errno)));
				goto bailout;
			}
			LOGS(L_PROXY,L_MINOR,("got SplitFile block #%x (%s)\n",
				(unsigned)n, str));
		}
		temp_close(&tfdata, 0);
		temp_close(&tfmeta, 0);
	}

	if (0 == reply->meta) {
		tf = &tfdata;
	} else {
		xfree(reply->content_type);
		reply->content_type = xstrdup("text/plain");
		tf = &tfmeta;
	}
	/* Empty temp file? */
	if (0 != (rc = temp_open(tf))) {
		LOGS(L_PROXY,L_ERROR,("no (meta)data for key %s (%s)\n",
			key_short(&key), strerror(errno)));
		reply->content_length = 0;
	} else {
		reply->content_length = tf->size;
	}

	if (reply->content_length > 0) {

		if (NULL != reply->content_type &&
			0 == strcmp(reply->content_type, "text/html")) {
			if (0 != (rc = proxy_html_filter(tf))) {
				xfree(reply->content_type);
				reply->content_type = xstrdup("text/plain");
				rc = 0;
			}
			reply->content_length = tf->wmax;
		} else {
			rc = 0;
		}
		if (0 != rc) {
			rc = proxy_503(conn, url);
		} else {
			reply->result_code = 200;
			reply->user = tf;
			reply->passthru = pass_tf;
			rc = proxy_reply(conn);
		}

	} else {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfmeta.name, strerror(errno)));
		rc = proxy_404(conn,url,uri);
	}

bailout:
	/* free any meta data */
	if (meta) {
		if (meta->data)
			xfree(meta->data);
		xfree(meta);
	}
	temp_close(&tfdata, 1);
	temp_close(&tfmeta, 1);
	xfree(block);
	return rc;
}

#define	VERSION NODE_NAME " " PACKAGE_VERSION

/*
 *	proxy_node_keypair()
 *	Create and print a SVK private and public keypair
 */
int proxy_node_keypair(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096, len;
	char *dst, *msg = NULL;
	char *fcpaddr = NULL;
	char *line = NULL;
	char *svk_private = NULL;
	char *svk_public = NULL;
	conn_t *fcp;
	int rc = 0;
	FUN("proxy_node_keypair");

	(void)url;

	fcp = (conn_t *)xcalloc(1, sizeof(conn_t));

	len = strlen(g_conf->fcphost) + 1 + 8 + 1;
	fcpaddr = xcalloc(len, sizeof(char));
	pm_snprintf(fcpaddr, len, "%s:%d", g_conf->fcphost, g_conf->fcpport);
	if (0 != (rc = sock_outgoing(fcp, g_conf->fcphost, g_conf->fcpport, 0))) {
		LOGS(L_PROXY,L_ERROR,("could not connect to %s\n", fcpaddr));
		rc = proxy_502(conn, fcpaddr);
		goto bailout;
	}
	LOGS(L_PROXY,L_MINOR,("connected to FCP server %s\n", fcpaddr));

	if (0 != (rc = sock_writeall(fcp, "\0\0\1\2", 4))) {
		LOGS(L_PROXY,L_ERROR,("could not send FCP header\n"));
		goto bailout;
	}
	if (0 != (rc = sock_printf(fcp, "GenerateSVKPair\n"))) {
		LOGS(L_PROXY,L_ERROR,("could not send 'GenerateSVKPair'\n"));
		goto bailout;
	}
	if (0 != (rc = sock_printf(fcp, "EndMessage\n"))) {
		LOGS(L_PROXY,L_ERROR,("could not send 'EndMessage'\n"));
		goto bailout;
	}

	while (0 == (rc = sock_agets(fcp, &line))) {
		char *var, *val;
		LOGS(L_PROXY,L_MINOR,("fcp: %s\n", line));
		var = strtok(line, "=\r\n");
		val = strtok(NULL, "\r\n");
		if (NULL != var) {
			if (0 == strcasecmp(var, "PublicKey")) {
				xfree(svk_public);
				svk_public = xstrdup(val);
			} else if (0 == strcasecmp(var, "PrivateKey")) {
				xfree(svk_private);
				svk_private = xstrdup(val);
			} else if (0 == strcasecmp(var, "EndMessage")) {
				xfree(line);
				break;
			}
		}
		xfree(line);
	}

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT3("  <title>%s - %s</title>\n",
		VERSION,
		LO("Create key pair"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("Create key pair")));
	OUT1("  <div align=\"center\">\n");
	OUT2("   <form method=\"GET\" action=\"%s\">\n",
		url);
	OUT1("    <table>\n");
	OUT1("     <tr class=\"head\">\n");
	OUT3("     <th class=\"center\" colspan=\"2\">%s %s</th>\n",
		LO("FCP-Server"),
		fcpaddr);
	OUT1("     </tr>\n");
	OUT1("     <tr class=\"odd\">\n");
	OUT2("      <th class=\"var\">%s</th>\n",
		LO("Public key"));
	OUT1("      <td class=\"left\">\n");
	OUT2("       <input type=\"text\" size=\"29\" name=\"public_key\" value=\"%s\" readonly=\"readonly\" />\n",
		svk_public);
	OUT2("       <b>%s</b>\n",
		"BCMA");
	OUT1("      </td>\n");
	OUT1("     </tr>\n");
	OUT1("     <tr class=\"even\">\n");
	OUT2("      <th class=\"var\">%s</th>\n",
		LO("Private key"));
	OUT1("      <td class=\"left\">\n");
	OUT2("       <input type=\"text\" size=\"29\" name=\"private_key\" value=\"%s\" readonly=\"readonly\" />\n",
		svk_private);
	OUT1("      </td>\n");
	OUT1("     </tr>\n");
	OUT1("    </table>\n");
	OUT1("   </form>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

bailout:
	sock_shutdown(fcp);
	xfree(svk_private);
	xfree(svk_public);
	xfree(msg);
	xfree(line);
	xfree(fcpaddr);
	xfree(fcp);
	return rc;
}

typedef enum form_e {
	FORM_STRING,
	FORM_EDITOR,
	FORM_PASSWD,
	FORM_INT,
	FORM_UINT8,
	FORM_UINT16,
	FORM_UINT32,
	FORM_UINT64,
	FORM_DOUBLE,
	FORM_COMBO,
	FORM_RADIO,
	FORM_CHECK
}	form_t;

typedef struct config_form_s {
	int runtime;
	const char *name;
	form_t type;
	size_t size;
	size_t maxlength;
	void *unit;
	void *data;
}	config_form_t;

typedef struct arr_handler_s {
	size_t first;
	size_t (*size)(void);
	const char *(*name)(size_t indx);
	int (*enabled)(size_t indx);
	int (*selected)(size_t indx);
}	arr_handler_t;

/*****************************************************************************
 *	niceness form handlers
 *****************************************************************************/
static size_t arr_niceness_size(void)
{
	return 21;
}

static const char *arr_niceness_name(size_t indx)
{
	static char buff[4];
	pm_snprintf(buff, sizeof(buff), "%d", (int)indx);
	return buff;
}

static int arr_niceness_enabled(size_t indx)
{
	(void)indx;
	return 1;
}

static int arr_niceness_selected(size_t indx)
{
	return (indx == (size_t)g_conf->niceness) ? 1 : 0;
}

static arr_handler_t arr_niceness = {
	0,
	arr_niceness_size,
	arr_niceness_name,
	arr_niceness_enabled,
	arr_niceness_selected
};

/*****************************************************************************
 *	crypto module form handlers
 *****************************************************************************/
static size_t arr_crypto_default_size(void)
{
	size_t n;
	for (n = 0; NULL != crypto_module_name(n); n++)
		;
	return n;
}

static const char *arr_crypto_default_name(size_t indx)
{
	return crypto_module_name(indx);
}

static int arr_crypto_default_enabled(size_t indx)
{
	return 0 == crypto_module_supp(indx) ? 0 : 1;
}

static int arr_crypto_default_selected(size_t indx)
{
	const char *name = crypto_module_name(indx);
	if (NULL != name) {
		return (0 == strcmp(g_conf->crypto_default, name)) ? 1 : 0;
	}
	return 0;
}

static arr_handler_t arr_crypto_default = {
	0,
	arr_crypto_default_size,
	arr_crypto_default_name,
	arr_crypto_default_enabled,
	arr_crypto_default_selected
};

/*****************************************************************************
 *	crypto reject form handlers
 *****************************************************************************/
static size_t arr_crypto_reject_size(void)
{
	size_t n;
	for (n = 0; NULL != crypto_module_name(n); n++)
		;
	return n;
}

static const char *arr_crypto_reject_name(size_t indx)
{
	return crypto_module_name(indx);
}

static int arr_crypto_reject_enabled(size_t indx)
{
	return 0 == crypto_module_supp(indx) ? 0 : 1;
}

static int arr_crypto_reject_selected(size_t indx)
{
	(void)indx;
	return 0;
}

static arr_handler_t arr_crypto_reject = {
	0,
	arr_crypto_reject_size,
	arr_crypto_reject_name,
	arr_crypto_reject_enabled,
	arr_crypto_reject_selected
};

/*****************************************************************************
 *	loglevel form handlers
 *****************************************************************************/
static size_t arr_loglevel_size(void)
{
	return 6;
}

static const char *arr_loglevel_name(size_t indx)
{
	switch (indx) {
	case 0: return "none";
	case 1: return "error";
	case 2: return "normal";
	case 3: return "minor";
	case 4: return "debug";
	}
	return "debugx";
}

static int arr_loglevel_enabled(size_t indx)
{
	if (indx < arr_loglevel_size()) {
		return 1;
	}
	return 0;
}

static int arr_loglevel_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_CORE] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_mem_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_MEM] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_shm_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_SHM] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_sock_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_SOCK] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_crypto_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_CRYPTO] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_peer_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_PEER] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_store_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_STORE] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_file_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_FILE] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_client_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_CLIENT] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_proxy_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_PROXY] == (int)indx) ? 1 : 0;
}

static int arr_loglevel_news_selected(size_t indx)
{
	return (g_conf->loglevel[LOGSECTION_NEWS] == (int)indx) ? 1 : 0;
}

static arr_handler_t arr_loglevel = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_selected
};

static arr_handler_t arr_loglevel_mem = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_mem_selected
};

static arr_handler_t arr_loglevel_shm = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_shm_selected
};

static arr_handler_t arr_loglevel_sock = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_sock_selected
};

static arr_handler_t arr_loglevel_crypto = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_crypto_selected
};

static arr_handler_t arr_loglevel_peer = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_peer_selected
};

static arr_handler_t arr_loglevel_store = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_store_selected
};

static arr_handler_t arr_loglevel_file = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_file_selected
};

static arr_handler_t arr_loglevel_client = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_client_selected
};

static arr_handler_t arr_loglevel_proxy = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_proxy_selected
};

static arr_handler_t arr_loglevel_news = {
	0,
	arr_loglevel_size,
	arr_loglevel_name,
	arr_loglevel_enabled,
	arr_loglevel_news_selected
};

char nodeaddr[32];
configuration_t form_config;

static const config_form_t form[] = {
	{0,"nodename",       FORM_STRING, 40, 255,
		NULL,         form_config.nodename},
	{0,"nodeport",       FORM_INT,    6,  6,
		NULL,         &form_config.nodeport},
	{0,"address",        FORM_STRING, 0,  0,
		NULL,         nodeaddr},
	{1,"niceness",       FORM_COMBO,  1,  0,
		&arr_niceness,&form_config.niceness},
	{1,"bwlimit_in",     FORM_UINT32, 12, 12,
		"bytes/sec",  &form_config.bwlimit_in},
	{1,"bwlimit_out",    FORM_UINT32, 12, 12,
		"bytes/sec",  &form_config.bwlimit_out},
	{1,"bwlimit_daily",  FORM_UINT64, 12, 12,
		"bytes",      &form_config.bwlimit_daily},
	{0,"seednodes",      FORM_STRING, 80, 255,
		NULL,         form_config.seednodes},
	{0,"configuration",  FORM_STRING, 80, 255,
		NULL,         form_config.configfile},
	{0,"logfile",        FORM_STRING, 80, 255,
		NULL,         form_config.logfile},
	{0,"runpath",        FORM_STRING, 80, 255,
		NULL,         form_config.runpath},
	{0,"temppath",       FORM_STRING, 80, 255,
		NULL,         form_config.temppath},
	{0,"storepath",      FORM_STRING, 80, 255,
		NULL,         form_config.storepath},
	{0,"storesize",      FORM_UINT64, 12, 12,
		"bytes",      &form_config.storesize},
	{0,"storedepth",     FORM_UINT32, 12, 12,
		NULL,         &form_config.storedepth},
	{1,"crypto_default", FORM_COMBO,  1,  0,
		&arr_crypto_default, &form_config.crypto_default},
	{0,"crypto_reject",  FORM_CHECK,  1,  0,
		&arr_crypto_reject, &form_config.crypto_reject},
	{1,"loglevel",       FORM_COMBO,  1,  0,
		&arr_loglevel,&form_config.loglevel[LOGSECTION_CORE]},
	{1,"loglevel_mem",   FORM_COMBO,  1,  0,
		&arr_loglevel_mem,&form_config.loglevel[LOGSECTION_MEM]},
	{1,"loglevel_shm",   FORM_COMBO,  1,  0,
		&arr_loglevel_shm,&form_config.loglevel[LOGSECTION_SHM]},
	{1,"loglevel_sock",  FORM_COMBO,  1,  0,
		&arr_loglevel_sock,&form_config.loglevel[LOGSECTION_SOCK]},
	{1,"loglevel_crypto",FORM_COMBO,  1,  0,
		&arr_loglevel_crypto,&form_config.loglevel[LOGSECTION_CRYPTO]},
	{1,"loglevel_peer",  FORM_COMBO,  1,  0,
		&arr_loglevel_peer,&form_config.loglevel[LOGSECTION_PEER]},
	{1,"loglevel_file",  FORM_COMBO,  1,  0,
		&arr_loglevel_file,&form_config.loglevel[LOGSECTION_FILE]},
	{1,"loglevel_store", FORM_COMBO,  1,  0,
		&arr_loglevel_store,&form_config.loglevel[LOGSECTION_STORE]},
	{1,"loglevel_client",FORM_COMBO,  1,  0,
		&arr_loglevel_client,&form_config.loglevel[LOGSECTION_CLIENT]},
	{1,"loglevel_proxy", FORM_COMBO,  1,  0,
		&arr_loglevel_proxy,&form_config.loglevel[LOGSECTION_PROXY]},
	{1,"loglevel_news",  FORM_COMBO,  1,  0,
		&arr_loglevel_news,&form_config.loglevel[LOGSECTION_NEWS]},
	{1,"maxhtl",         FORM_UINT32, 12, 12,
		"2 - 25",     &form_config.maxhtl},
	{1,"fec_queuesize",  FORM_INT,    12, 12,
		"0 (off) - 256", &form_config.fec_queuesize},
	{0,"fcphost",        FORM_STRING, 40, 255,
		NULL,         form_config.fcphost},
	{0,"fcpport",        FORM_INT,    6,  6,
		NULL,         &form_config.fcpport},
	{1,"fcpretries",     FORM_INT,    6,  6,
		"1 - 5",      &form_config.fcpretries},
	{0,"proxyhost",      FORM_STRING, 40, 255,
		NULL,         form_config.proxyhost},
	{0,"proxyport",      FORM_INT,    6,  6,
		NULL,         &form_config.proxyport},
	{1,"proxyhtl",       FORM_UINT32, 12, 12,
		NULL,         &form_config.proxyhtl},
	{1,"proxycache",     FORM_UINT32, 12, 12,
		"0 = off, or 1 - 64", &form_config.proxycache},
	{1,"news_base",      FORM_STRING, 40, 255,
		NULL,         form_config.news_base},
	{1,"news_boards",    FORM_EDITOR, 80, 3,
		"comma sep. list", form_config.news_boards},
	{1,"news_ignore",    FORM_EDITOR, 80, 3,
		"comma sep. list", form_config.news_ignore},
	{1,"news_days",      FORM_INT,    12, 12,
		"1 - ??",     &form_config.news_days},
	{1,"news_tries",     FORM_INT,    12, 12,
		"1 - ??",     &form_config.news_tries},
	{1,"news_skip",      FORM_INT,    12, 12,
		"1 - 5",      &form_config.news_skip},
	{1,"language",       FORM_STRING, 2,  2,
		"de (German) or en (English)",
		form_config.language},
	{1,"password",       FORM_PASSWD, 8,  8,
		NULL,         NULL},
	{0,NULL,             0,           0,  0,
		NULL,         NULL}
};

static void proxy_form_string(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	const char *_str = (const char *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%s\" %s/>\n",
			f->size, f->maxlength, f->name, _str,
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %s\n", _str);
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_editor(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	const char *_str = (const char *)f->data;
	if (f->size > 0) {
		OUT6("       <textarea name=\"%s\" cols=\"%d\" rows=\"%d\" %s>" \
			"%s</textarea>\n",
			f->name, f->size, f->maxlength,
			f->runtime ? "" : "readonly=\"readonly\" ",
			_str);
	} else {
		OUT2("      %s\n", _str);
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_passwd(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	if (f->size > 0) {
		OUT5("       <input type=\"password\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\"%s />\n",
			f->size, f->maxlength, f->name,
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %s\n", "????????");
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_int(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	int _int = *(int *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%d\" %s/>\n",
			f->size, f->maxlength, f->name, _int,
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %d\n", _int);
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_uint8(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	uint8_t _ui8 = *(uint8_t *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%u\" %s/>\n",
			f->size, f->maxlength, f->name, _ui8,
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %u\n", _ui8);
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_uint16(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	uint16_t _ui16 = *(uint16_t *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%u\" %s/>\n",
			f->size, f->maxlength, f->name, _ui16,
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %u\n", _ui16);
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_uint32(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	uint32_t _ui32 = *(uint32_t *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%s\" %s/>\n",
			f->size, f->maxlength, f->name, size_KMG(_ui32),
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %s\n", size_KMG(_ui32));
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_uint64(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	uint64_t _ui64 = *(uint64_t *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%s\" %s/>\n",
			f->size, f->maxlength, f->name, size_KMG(_ui64),
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %s\n", size_KMG(_ui64));
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_double(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	double _dbl = *(double *)f->data;
	if (f->size > 0) {
		OUT6("       <input type=\"text\" size=\"%d\"" \
			" maxlength=\"%d\" name=\"%s\" value=\"%s\" %s/>\n",
			f->size, f->maxlength, f->name, size_KMG(_dbl),
			f->runtime ? "" : "readonly=\"readonly\" ");
	} else {
		OUT2("      %s\n", size_KMG(_dbl));
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_combo(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	const arr_handler_t *a = (arr_handler_t *)f->unit;
	size_t i;

	OUT3("       <select name=\"%s\" size=\"%d\">\n",
		f->name, f->size);
	for (i = a->first; i < a->size() + a->first; i++) {
		OUT4("        <option value=\"%s\"%s>%s</option>\n",
			a->name(i),
			a->selected(i) ? " selected=\"selected\"" : "",
			a->name(i));
	}
	OUT1("       </select>\n");
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_radio(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	const arr_handler_t *a = (arr_handler_t *)f->unit;
	size_t i;

	for (i = a->first; i < a->size() + a->first; i++) {
		OUT5("       <input type=\"radio\" name=\"%s\" value=\"%s\" %s/> %s\n",
			f->name,
			a->name(i),
			a->selected(i) ? "selected=\"selected\" " : "",
			a->name(i));
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

static void proxy_form_check(char **pdst, char **pmsg, size_t *pmax,
	const config_form_t *f)
{
	char *dst = *pdst;
	char *msg = *pmsg;
	size_t max = *pmax;
	const arr_handler_t *a = (arr_handler_t *)f->unit;
	size_t i;

	for (i = a->first; i < a->size() + a->first; i++) {
		OUT6("       <input type=\"checkbox\" name=\"%s\" value=\"%s\"%s%s /> %s\n",
			f->name,
			a->name(i),
			f->runtime ? "" : " readonly=\"readonly\"",
			a->enabled(i) ? " checked=\"checked\"" : "",
			a->name(i));
	}
	*pdst = dst;
	*pmsg = msg;
	*pmax = max;
}

/*
 *	proxy_node_config()
 *	Print the configuration for this node
 */
int proxy_node_config(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	const config_form_t *f;
	int n, rc = 0;
	FUN("proxy_node_config");

	pm_snprintf(nodeaddr, sizeof(nodeaddr), "%s:%d",
		inet_ntoa(g_conf->node.sin_addr),
		ntohs(g_conf->node.sin_port));
	form_config = *g_conf;

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT5("  <title>%s %s %s:%d</title>\n",
		VERSION,
		LO("Node configuration"),
		g_conf->nodename,
		g_conf->nodeport);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n",
		nav(reply, "subtitle", "%s <i>%s:%d</i>",
			LO("Node configuration"),
			g_conf->nodename,
			g_conf->nodeport));
	OUT1("  <div align=\"center\">\n");
	OUT2("   <form method=\"get\" action=\"/%s\">\n",
		url);
	OUT1("    <table cellspacing=\"1\" cellpadding=\"1\">\n");

	OUT1("     <tr class=\"head\">\n");
	OUT2("      <th class=\"left\">%s</th>\n",
		LO("option"));
	OUT2("      <th class=\"left\">%s</th>\n",
		LO("value"));
	OUT1("     </tr>\n");
	for (f = form, n = 0; NULL != f->name; f++, n++) {
		LOGS(L_PROXY,L_MINOR,("form %p: name='%s', type=%d, size=%d, maxlength=%d, unit=%p, data=%p\n",
			f, f->name, (int)f->type, (int)f->size, (int)f->maxlength,
			f->unit, f->data));
		OUT2("     <tr class=\"%s\">\n",
			(n & 1) ? "odd" : "even");
		OUT3("      <th class=\"%s\">%s</th>\n",
			(const char *)(f->runtime ? "in" : "out"),
			f->name);
		OUT1("      <td class=\"left\">\n");
		switch (f->type) {
		case FORM_STRING:
			proxy_form_string(&dst, &msg, &max, f);
			break;
		case FORM_EDITOR:
			proxy_form_editor(&dst, &msg, &max, f);
			break;
		case FORM_PASSWD:
			proxy_form_passwd(&dst, &msg, &max, f);
			break;
		case FORM_INT:
			proxy_form_int(&dst, &msg, &max, f);
			break;
		case FORM_UINT8:
			proxy_form_uint8(&dst, &msg, &max, f);
			break;
		case FORM_UINT16:
			proxy_form_uint16(&dst, &msg, &max, f);
			break;
		case FORM_UINT32:
			proxy_form_uint32(&dst, &msg, &max, f);
			break;
		case FORM_UINT64:
			proxy_form_uint64(&dst, &msg, &max, f);
			break;
		case FORM_DOUBLE:
			proxy_form_double(&dst, &msg, &max, f);
			break;
		case FORM_COMBO:
			proxy_form_combo(&dst, &msg, &max, f);
			break;
		case FORM_RADIO:
			proxy_form_radio(&dst, &msg, &max, f);
			break;
		case FORM_CHECK:
			proxy_form_check(&dst, &msg, &max, f);
			break;
		}
		if (FORM_COMBO != f->type &&
			FORM_RADIO != f->type &&
			FORM_CHECK != f->type &&
			NULL != f->unit) {
			OUT2("       %s\n", f->unit);
		}
		OUT1("      </td>\n");
		OUT1("     </tr>\n");
	}
	OUT1("     <tr class=\"head\">\n");
	OUT2("      <th class=\"right\">%s</th>\n",
		LO("confirm"));
	OUT1("      <td class=\"center\">\n");
	OUT2("       <input type=\"submit\" name=\"submit\" value=\"%s\" />\n",
		LO("submit"));
	OUT1("      </td>\n");
	OUT1("     </tr>\n");
	OUT1("    </table>\n");
	OUT1("   </form>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_node_config_set()
 *	Set a key=value pair, if possible.
 */
int proxy_node_config_set(const char *key, const char *val)
{
	const config_form_t *f;
	char *line = NULL;
	FUN("proxy_node_config_set");

	for (f = form; NULL != f->name; f++) {
		/* skip if this isn't the field */
		if (0 != strcasecmp(key, f->name))
			continue;
		/* runtime configurable? */
		if (1 == f->runtime) {
			pm_asprintf(&line, "%s=%s", f->name, val);
			conf_read_line(line, "proxy", &form_config);
			xfree(line);
		}
		return 0;
	}
	return -1;
}

/*
 *	proxy_node_process()
 *	Collect and print some process status info.
 */
static int proxy_node_process(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t i, j, max = 4096;
	char *dst, *msg = NULL;
	char *timestr = xcalloc(LINESIZE, sizeof(char));
	proc_t proc;
	time_t t0, diff;
	int rc;
	FUN("proxy_node_process");

	(void)url;

	t0 = time(NULL);

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT3("  <title>%s %s</title>\n",
		VERSION,
		LO("Process info"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("Process info")));
	OUT1("  <div align=\"center\">\n");
	OUT1("   <table>\n");
	OUT1("    <tr class=\"head\">\n");
	OUT2("     <th class=\"left\">%s</th>\n", LO("PID"));
	OUT2("     <th class=\"left\">%s</th>\n", LO("start time"));
	OUT2("     <th class=\"left\">%s</th>\n", LO("running"));
	OUT2("     <th class=\"left\">%s</th>\n", LO("name"));
	OUT1("    </tr>\n");

	for (i = 0, j = 0; i < MAXPROC; i++) {
		if (0 != proc_info(i, &proc)) {
			continue;
		}
		if (0 == proc.pid)
			continue;
		OUT2("    <tr class=\"%s\">\n", (j & 1) ? "odd" : "even");
		j++;
		OUT2("     <td>%d</td>\n",
			proc.pid);
		strftime(timestr, LINESIZE, "%Y-%m-%d %H:%M:%S GMT",
			gmtime(&proc.time));
		OUT2("     <td>%s</td>\n",
			timestr);
		diff = t0 - proc.time;
		pm_snprintf(timestr, LINESIZE, "%s",
			age_str(LANG,CHARSET,t0, proc.time));
		OUT2("     <td>%s</td>\n", timestr);
		OUT2("     <td>%s</td>\n", proc.name);
		OUT1("    </tr>\n");
	}

	OUT1("   </table>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");


	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	xfree(timestr);
	return rc;
}

/* write into a tempfile_t */
static int write_tf(void *user, uint8_t data)
{
	tempfile_t *tf = (tempfile_t *)user;
	return temp_write(tf, &data, 1);
}

static int bandwidth_colors[16] = {
	0x001050,	/* 0 */
	0x002051,	/* 1 */
	0x003053,	/* 2 */
	0x004057,	/* 3 */
	0x00605c,	/* 4 */
	0x008060,	/* 5 */
	0x00a064,	/* 6 */
	0x00b068,	/* 7 */
	0x00c06c,	/* 8 */
	0x00d070,	/* 9 */
	0x00e074,	/* a */
	0x00f078,	/* b */
	0x00ff7c,	/* c */
	0xa0a000,	/* d */
	0xb08000,	/* e */
	0xc02000	/* f */
};

/*
 *	proxy_node_bandwidth_png()
 *	Return an internally generated PNG for 60s bandwidth values
 */
static int proxy_node_bandwidth_png(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	char *src;
	uint8_t bandwidth[30];
	size_t i, j;
	tempfile_t tfpng;
	char *filename = NULL;
	png_t *png = NULL;
	int dir, x, y, rc = 0;
	FUN("proxy_node_bandwidth_png");

	temp_invalid(&tfpng);
	LOGS(L_PROXY,L_DEBUG,("show bandwidth %s\n", url));
	src = strstr(url, "bandwidth");
	if (NULL == src) {
		LOGS(L_PROXY,L_ERROR,("'bandwidth' not found in URL '%s'\n",
			url));
		rc = -1;
		goto bailout;
	}
	src += strlen("bandwidth");

	memset(bandwidth, 0, sizeof(bandwidth));
	if (*src == 'i') {
		dir = 1;
	} else if (*src == 'o') {
		dir = 2;
	} else {
		dir = 0;
	}
	for (i = 0; *src && i < 60; i++) {
		switch (*src) {
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			bandwidth[i/2] |= *src - '0';
			break;
		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
			bandwidth[i/2] |= *src - 'A' + 10;
			break;
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
			bandwidth[i/2] |= *src - 'a' + 10;
			break;
		}
		if (0 == (i % 2)) {
			bandwidth[i/2] *= 16;
		}
		src++;
	}

	if (0 != (rc = temp_binary(&tfpng, "bandwidth", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}
	filename = xcalloc(MAXPATHLEN, sizeof(char));
	pm_snprintf(filename, MAXPATHLEN, "%snode/bar.png",
		g_conf->configpath);
	if (NULL == (png = png_read(filename, &tfpng, write_tf))) {
		LOGS(L_PROXY,L_ERROR,("png_read('%s') call failed!\n", filename));
		goto bailout;
	}
	switch (dir) {
	case 1:
		png_tinted_rectangle(png, 0, 0, png->w-1, png->h-1,
			PNG_RGB(240,255,240), 255);
		for (x = 0; x < 5; x++) {
			for (y = -x; y <= +x; y++)
				png_put_pixel(png, 2 + x, 8 + y, PNG_RGB(0,0,0), 255);
		}
		break;
	case 2:
		png_tinted_rectangle(png, 0, 0, png->w-1, png->h-1,
			PNG_RGB(255,240,240), 255);
		for (x = 0; x < 5; x++) {
			for (y = -x; y <= +x; y++)
				png_put_pixel(png, png->w - 2 - x, 8 + y, PNG_RGB(0,0,0), 255);
		}
		break;
	}
	for (i = 0; i < 60; i++) {
		size_t col = bandwidth[i/2];
		if (0 == (i % 2)) {
			col /= 16;
		} else {
			col %= 16;
		}
		for (j = 0; j < 16; j++) {
			if (j < col) {
				if (1 == dir) {
					png_put_pixel(png, (png->w - 60) / 2 + i, 15-j,
						bandwidth_colors[j], 255);
				} else {
					png_put_pixel(png, (png->w - 60) / 2 + 59 - i, 15-j,
						bandwidth_colors[j], 255);
				}
			}
		}
	}
	png_finish(png);
	temp_close(&tfpng, 0);

	if (0 != (rc = temp_open(&tfpng))) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("image/png");
	reply->content_length = tfpng.size;
	reply->user = &tfpng;
	reply->passthru = pass_tf;
	rc = proxy_reply(conn);

bailout:
	temp_close(&tfpng, 1);
	xfree(filename);
	return rc;
}

static char xdigit_pixels[16][7][5] = {
	{
	" xxx ",
	"x   x",
	"x  xx",
	"x x x",
	"xx  x",
	"x   x",
	" xxx "
	},{
	"  x  ",
	" xx  ",
	"x x  ",
	"  x  ",
	"  x  ",
	"  x  ",
	"xxxxx"
	},{
	" xxx ",
	"x   x",
	"    x",
	" xxx ",
	"x    ",
	"x    ",
	"xxxxx"
	},{
	"xxxxx",
	"   x ",
	"  x  ",
	" xxx ",
	"    x",
	"x   x",
	" xxx "
	},{
	"   x ",
	"  xx ",
	" x x ",
	"x  x ",
	"xxxxx",
	"   x ",
	"   x "
	},{
	"xxxxx",
	"x    ",
	"x    ",
	"xxxx ",
	"    x",
	"x   x",
	" xxx "
	},{
	"  xx ",
	" x   ",
	"x    ",
	"xxxx ",
	"x   x",
	"x   x",
	" xxx "
	},{
	"xxxxx",
	"    x",
	"   x ",
	"  x  ",
	" x   ",
	"x    ",
	"x    "
	},{
	" xxx ",
	"x   x",
	"x   x",
	" xxx ",
	"x   x",
	"x   x",
	" xxx "
	},{
	" xxx ",
	"x   x",
	"x   x",
	" xxxx",
	"    x",
	"   x ",
	" xx  "
	},{
	"  x  ",
	" x x ",
	"x   x",
	"xxxxx",
	"x   x",
	"x   x",
	"x   x"
	},{
	"xxxx ",
	"x   x",
	"x   x",
	"xxxx ",
	"x   x",
	"x   x",
	"xxxx "
	},{
	" xxxx",
	"x   x",
	"x    ",
	"x    ",
	"x    ",
	"x   x",
	" xxxx"
	},{
	"xxx  ",
	"x  x ",
	"x   x",
	"x   x",
	"x   x",
	"x  x ",
	"xxx  "
	},{
	"xxxxx",
	"x    ",
	"x    ",
	"xxx  ",
	"x    ",
	"x    ",
	"xxxxx"
	},{
	"xxxxx",
	"x    ",
	"x    ",
	"xxx  ",
	"x    ",
	"x    ",
	"x    "
	}
};

#define	FONT_W	5
#define	FONT_H	7
#define	CELL_W	(FONT_W+2+1)
#define	CELL_H	5
#define	FPR_W	(CELL_W*16+1)
#define	FPR_H	(CELL_H+FONT_H+2+3)

/*
 *	proxy_node_fingerprint_png()
 *	Return an internally generated PNG for a fingerprint
 */
static int proxy_node_fingerprint_png(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	char *src;
	uint8_t fingerprint[16];
	size_t i, j, x;
	tempfile_t tfpng;
	png_t *png = NULL;
	int rc = 0;
	FUN("proxy_node_fingerprint_png");

	temp_invalid(&tfpng);

	LOGS(L_PROXY,L_DEBUG,("show fingerprint %s\n", url));
	src = strstr(url, "fingerprint");
	if (NULL == src) {
		LOGS(L_PROXY,L_ERROR,("'fingerprint' not found in URL '%s'\n",
			url));
		rc = -1;
		goto bailout;
	}
	src += strlen("fingerprint");

	memset(fingerprint, 0, sizeof(fingerprint));
	for (i = 0; *src && i < 2 * 16; i++, src++) {
		fingerprint[i/2] *= 16;
		switch (*src) {
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			fingerprint[i/2] |= *src - '0';
			break;
		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
			fingerprint[i/2] |= *src - 'A' + 10;
			break;
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
			fingerprint[i/2] |= *src - 'a' + 10;
			break;
		}
	}

	if (0 != (rc = temp_binary(&tfpng, "fingerprint", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}
	if (NULL == (png = png_create(FPR_W, FPR_H,
		COLOR_GRAYSCALE, 8, &tfpng, write_tf))) {
		LOGS(L_PROXY,L_ERROR,("png_create('%s') call failed!\n", tfpng.name));
		goto bailout;
	}
	png_filled_rectangle(png, 0, 0, FPR_W-1, FPR_H-1, 0x00, 0);
	for (i = 0; i < 16; i++) {
		int col = fingerprint[i];
		png_filled_rectangle(png, CELL_W*i+1, 1,
			CELL_W*(i+1)-1, CELL_H-1, col, 0);
		png_filled_rectangle(png, CELL_W*i+1, CELL_H+1,
			CELL_W*(i+1)-1, FPR_H-2, 0xf0, 0);
		for (j = 0; j < 7; j++) {
			for (x = 0; x < 5; x++) {
				if (xdigit_pixels[i][j][x] == 'x')
					png_put_pixel(png, CELL_W*i+2+x, CELL_H+2+j, 0x00, 0);
			}
		}
	}
	png_finish(png);
	temp_close(&tfpng, 0);

	if (0 != (temp_open(&tfpng))) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("image/png");
	reply->content_length = tfpng.size;
	reply->user = &tfpng;
	reply->passthru = pass_tf;
	rc = proxy_reply(conn);

bailout:
	temp_close(&tfpng, 1);
	return rc;
}

/*
 *	proxy_node_peers()
 *	Collect and print some peers status information.
 */
static int proxy_node_peers(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t i, n, max = 4096;
	char *dst, *msg = NULL;
	time_t t0, diff;
	peer_info_t *info = calloc(1, sizeof(peer_info_t));
	uint64_t sum, in_total, out_total, sum_in_min_avg, sum_out_min_avg;
	uint64_t adv_out_total, adv_in_total, adv_out_min_avg, adv_in_min_avg;
	uint64_t req_out_total, req_in_total, req_out_min_avg, req_in_min_avg;
	uint64_t ins_out_total, ins_in_total, ins_out_min_avg, ins_in_min_avg;
	uint64_t fec_out_total, fec_in_total, fec_out_min_avg, fec_in_min_avg;
	uint64_t count, daily_in_out, total_in_out;
	unsigned pct, hit_pct=0, bad_pct=0, ign_pct=0, miss_pct=0, fwd_pct=0;
	size_t in_conn, out_conn, fingerprint_cnt;
	uint32_t fingerprint_sum[16];
	uint8_t fingerprint[16];
	size_t bandwidth_scale;
	uint32_t bandwidth_sum;
	uint8_t bandwidth[30];
	char dir;
	int rc;
	FUN("proxy_node_peers");

	(void)url;

	t0 = time(NULL);

	in_total = 0;
	out_total = 0;
	sum_in_min_avg = 0;
	sum_out_min_avg = 0;

	adv_out_total = 0;
	adv_in_total = 0;
	adv_out_min_avg = 0;
	adv_in_min_avg = 0;

	req_out_total = 0;
	req_in_total = 0;
	req_out_min_avg = 0;
	req_in_min_avg = 0;

	ins_out_total = 0;
	ins_in_total = 0;
	ins_out_min_avg = 0;
	ins_in_min_avg = 0;

	fec_out_total = 0;
	fec_in_total = 0;
	fec_out_min_avg = 0;
	fec_in_min_avg = 0;

	in_conn = 0;
	out_conn = 0;

	memset(fingerprint_sum, 0, sizeof(fingerprint_sum));
	fingerprint_cnt = 0;

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT3("  <title>%s %s</title>\n",
		VERSION,
		LO("peer status"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("Peer status")));
	OUT1("  <div align=\"center\">\n");
	OUT1("  <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"0\">\n");
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th>%s</th>\n",
		LO("Address"));
	OUT3("    <th>%s/%s</th>\n",
		LO("in"),
		LO("out"));
	OUT1("    <th width=\"15%%\"><a href=\"/node/peers.html?legend=1#ADV\">ADV</a></th>\n");
	OUT1("    <th width=\"15%%\"><a href=\"/node/peers.html?legend=1#REQ\">REQ</a></th>\n");
	OUT1("    <th width=\"15%%\"><a href=\"/node/peers.html?legend=1#INS\">INS</a></th>\n");
	OUT1("    <th width=\"15%%\"><a href=\"/node/peers.html?legend=1#FEC\">FEC</a></th>\n");
	OUT1("   </tr>\n");

	for (n = 0, rc = 0; rc == 0; n++) {
		if (0 != peer_info(n,info))
			break;
		if (0 == info->active)
			continue;

		dir = (info->queue.size == 0) ? 'i' : 'o';

		OUT2("   <tr class=\"%s\">\n",
			(n & 1) ? "odd" : "even");
		if (strlen(info->peername) > 0) {
			OUT2("       <td colspan=\"6\" class=\"head\">%s</td>\n",
				info->peername);
		} else {
			OUT2("       <td colspan=\"6\" class=\"head\">[%s]</td>\n",
				LO("does not resolve"));
		}
		OUT1("   </tr>\n");

		OUT2("   <tr class=\"%s\">\n",
			(n & 1) ? "odd" : "even");

		/* peer IP and port; node version; crypto; last input/output time */
		OUT1("    <td class=\"right\">\n");
		OUT1("     <table width=\"100%%\" cellspacing=\"2\" cellpadding=\"0\">\n");
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#node\">%s</a></td>\n",
			LO("node"));
		OUT3("       <td class=\"val\">%s:%d</td>\n",
			inet_ntoa(info->address.sin_addr),
			ntohs(info->address.sin_port));
		OUT1("      </tr>\n");
/* version info */
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#version\">%s</a></td>\n",
			LO("version"));
		if ('\0' != info->type[0]) {
			OUT6("       <td class=\"val\">%s-%d.%d.%d-%d</td>\n",
				info->type, info->major,
				info->minor/65536, info->minor%65536,
				info->build);
		} else {
			OUT2("       <td class=\"val\">%s</td>\n",
				LO("unknown"));
		}
		OUT1("      </tr>\n");

		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#status\">%s</a></td>\n",
			LO("status"));
		OUT2("       <td class=\"val\">%s</td>\n",
			conn_status_str(info->status));
		OUT1("      </tr>\n");

		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#crypto\">%s</a></td>\n",
			LO("crypto"));
		if ('\0' != info->type[0]) {
			OUT3("       <td class=\"val\">%s [%s]</td>\n",
				crypto_module_name(info->crypto_module_out),
				crypto_module_name(info->crypto_module_in));
		} else if (CONN_CONNECTED == info->status) {
			OUT2("       <td class=\"val\">%s</td>\n",
				crypto_module_name(info->crypto_module_out));
		} else {
			OUT2("       <td class=\"val\">%s</td>\n",
				LO("unknown"));
		}
		OUT1("      </tr>\n");

		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#times\">%s</a></td>\n",
			LO("times"));
		if (0 == info->last_io) {
			OUT2("       <td class=\"val\">%s</td>\n",
				LO("not yet"));
		} else {
			diff = (t0 > info->last_io) ? t0 - info->last_io : 0;
			OUT3("       <td class=\"val\">%s / %us</td>\n",
				age_str(LANG, CHARSET, t0, info->start),
				diff);
		}
		OUT1("      </tr>\n");

		if (dir == 'o') {
			size_t filled;
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#queue\">%s</a></td>\n",
				LO("queue"));
			filled = (info->queue.head >= info->queue.tail) ?
				info->queue.head - info->queue.tail :
				info->queue.head + info->queue.size - info->queue.tail;
			OUT3("       <td class=\"val\">%u (%u)</td>\n",
				filled,
				info->queue.size);
			OUT1("      </tr>\n");
		} else {
			if (0 != info->keycount.total) {
				uint64_t sum, maxsum = 0;
				/* create a 'fingerprint' from incoming msgs */
				for (i = 0; i < 16; i++) {
					sum = info->keycount.adv_count[i] +
						info->keycount.req_count[i] +
						info->keycount.ins_count[i];
					if (sum > maxsum) {
						maxsum = sum;
					}
				}
				maxsum += 1;
				for (i = 0; i < 16; i++) {
					info->fingerprint[i] =
						256 * (info->keycount.adv_count[i] +
						info->keycount.req_count[i] +
						info->keycount.ins_count[i]) /
						maxsum;
				}
			}
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#keys\">%s</a></td>\n",
				LO("keys"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->keycount.total);
			OUT1("      </tr>\n");
		}
		OUT1("     </table>\n");
		OUT1("    </td>\n");

		sum = info->stats.adv_minute[59] +
			info->stats.req_minute[59] +
			info->stats.ins_minute[59];

		OUT2("    <td class=\"%s\">\n",
			(info->queue.size > 0) ? "out" : "in");
		OUT1("     <table width=100%% cellspacing=1 cellpadding=1>\n");

		if (dir == 'i') {
			count = (PEERS_MAX - peer_in_count()) / 2 + 1;
			bandwidth_scale = g_conf->bwlimit_in;
		} else {
			count = (PEERS_MAX - peer_out_count()) / 2 + 1;
			bandwidth_scale = g_conf->bwlimit_out;
		}
		if (bandwidth_scale < 1)
			bandwidth_scale = 1;
		bandwidth_sum = 0;
		memset(bandwidth, 0, sizeof(bandwidth));
		if (bandwidth_scale > 0) {
			for (i = 0; i < 60; i++) {
				size_t scaled = (size_t)(count * 16 *
						(info->stats.adv_second[i] +
						info->stats.req_second[i] +
						info->stats.ins_second[i]) / bandwidth_scale);
				if (scaled > 15) {
					scaled = 15;
				}
				bandwidth_sum += scaled;
				if (0 == (i % 2)) {
					bandwidth[i/2] |= scaled << 4;
				} else {
					bandwidth[i/2] |= scaled;
				}
			}
		}

		if (dir == 'i') {
			OUT2("      <tr><td class=\"center\" width=\"20%%\">%s</td></tr>\n",
				LO("incoming"));
			OUT2("      <tr><td class=\"center\">%sB</td></tr>\n",
				round_KMG(info->in_peer));
			OUT2("      <tr><td class=\"center\">%sB/m</td></tr>\n",
				round_KMG(sum));
			sum_in_min_avg += sum;
			in_total += info->in_peer;
			in_conn++;
		} else {
			OUT2("      <tr><td class=\"center\" width=\"20%%\">%s</td></tr>\n",
				LO("outgoing"));
			OUT2("      <tr><td class=\"center\">%sB</td></tr>\n",
				round_KMG(info->out_peer));
			OUT2("      <tr><td class=\"center\">%sB/m</td></tr>\n",
				round_KMG(sum));
			sum_out_min_avg += sum;
			out_total += info->out_peer;
			out_conn++;
		}
		OUT5("      <tr><td class=\"center\"><img src=\"/node/fingerprint%s.png\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"[%s]\"/></td></tr>\n",
			hexstr(info->fingerprint, 16),
			FPR_W, FPR_H,
			hexstr(info->fingerprint, 16));
		if (info->storesize > 0) {
			pct = (unsigned)(info->currsize * 100 / info->storesize);
			OUT4("      <tr><td class=\"center\">%sB/%sB (%d%%)</td></tr>\n",
				round_KMG(info->currsize),
				round_KMG(info->storesize),
				pct);
		} else {
			OUT1("      <tr><td class=\"center\">-/-</td></tr>\n");
		}
		OUT4("      <tr><td class=\"center\"><img src=\"/node/bandwidth%c%s.png\" width=\"100\" height=\"16\" border=\"0\" alt=\"bandwidth:%03x\"/></td></tr>\n",
				dir,
				hexstr(bandwidth, sizeof(bandwidth)),
				bandwidth_sum);
		OUT1("     </table>\n");
		OUT1("    </td>\n");
		if (info->queue.size > 0) {
			for (i = 0; i < 16; i++)
				fingerprint_sum[i] += info->fingerprint[i];
			fingerprint_cnt += 1;
		}

/* ADV */
		OUT1("    <td class=\"right\">\n");
		OUT1("     <table width=100%% cellspacing=1 cellpadding=1>\n");
		if (dir == 'o') {
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.adv_msgs);
			OUT1("      </tr>\n");
		} else {	
			hit_pct = miss_pct = ign_pct = fwd_pct = 0;
			if (info->stats.adv_msgs > 0) {
				hit_pct = (unsigned)(100 *
					info->stats.adv_msgs_hit / info->stats.adv_msgs);
				miss_pct = (unsigned)(100 *
					info->stats.adv_msgs_miss / info->stats.adv_msgs);
				ign_pct = (unsigned)(100 *
					info->stats.adv_msgs_ign / info->stats.adv_msgs);
				fwd_pct = (unsigned)(100 *
					info->stats.adv_msgs_fwd / info->stats.adv_msgs);
			}
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.adv_msgs);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.hit\">%s</a></td>\n",
				LO("hit"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.adv_msgs_hit, hit_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.ign\">%s</a></td>\n",
				LO("ign"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.adv_msgs_ign, ign_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.miss\">%s</a></td>\n",
				LO("miss"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.adv_msgs_miss, miss_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.fwd\">%s</a></td>\n",
				LO("fwd"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.adv_msgs_fwd, fwd_pct);
			OUT1("      </tr>\n");
		}
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.size\">%s</a></td>\n",
			LO("size"));
		OUT2("       <td class=\"val\">%sB</td>\n",
			round_KMG(info->stats.adv_total));
		OUT1("      </tr>\n");
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#ADV.rate\">%s</a></td>\n",
			LO("rate"));
		OUT2("       <td class=\"val\">%sB/m</td>\n",
			round_KMG(info->stats.adv_minute[59]));
		OUT1("      </tr>\n");
		OUT1("     </table>\n");
		OUT1("    </td>\n");
		if (dir == 'o') {
			adv_out_total += info->stats.adv_total;
			adv_out_min_avg += info->stats.adv_minute[59];
		} else {
			adv_in_total += info->stats.adv_total;
			adv_in_min_avg += info->stats.adv_minute[59];
		}

/* REQ */
		OUT1("    <td class=\"right\">\n");
		OUT1("     <table width=100%% cellspacing=1 cellpadding=1>\n");
		if (dir == 'o') {
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.req_msgs);
			OUT1("      </tr>\n");
		} else {
			hit_pct = miss_pct = ign_pct = fwd_pct = 0;
			if (info->stats.req_msgs > 0) {
				hit_pct = (unsigned)(100 *
					info->stats.req_msgs_hit / info->stats.req_msgs);
				miss_pct = (unsigned)(100 *
					info->stats.req_msgs_miss / info->stats.req_msgs);
				ign_pct = (unsigned)(100 *
					info->stats.req_msgs_ign / info->stats.req_msgs);
				fwd_pct = (unsigned)(100 *
					info->stats.req_msgs_fwd / info->stats.req_msgs);
			}
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.req_msgs);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.hit\">%s</a></td>\n",
				LO("hit"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.req_msgs_hit, hit_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.ign\">%s</a></td>\n",
				LO("ign"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.req_msgs_ign, ign_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.miss\">%s</a></td>\n",
				LO("miss"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.req_msgs_miss, miss_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.fwd\">%s</a></td>\n",
				LO("fwd"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.req_msgs_fwd, fwd_pct);
			OUT1("      </tr>\n");
		}
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.size\">%s</a></td>\n",
			LO("size"));
		OUT2("       <td class=\"val\">%sB</td>\n",
			round_KMG(info->stats.req_total));
		OUT1("      </tr>\n");
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#REQ.rate\">%s</a></td>\n",
			LO("rate"));
		OUT2("       <td class=\"val\">%sB/m</td>\n",
			round_KMG(info->stats.req_minute[59]));
		OUT1("      </tr>\n");
		OUT1("     </table>\n");
		OUT1("    </td>\n");
		if (dir == 'o') {
			req_out_total += info->stats.req_total;
			req_out_min_avg += info->stats.req_minute[59];
		} else {
			req_in_total += info->stats.req_total;
			req_in_min_avg += info->stats.req_minute[59];
		}

/* INS */
		OUT1("    <td class=\"right\">\n");
		OUT1("     <table width=100%% cellspacing=1 cellpadding=1>\n");
		if (dir == 'o') {
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.ins_msgs);
			OUT1("      </tr>\n");
		} else {
			hit_pct = miss_pct = bad_pct = fwd_pct = 0;
			if (info->stats.ins_msgs > 0) {
				hit_pct = (unsigned)(100 *
					info->stats.ins_msgs_hit / info->stats.ins_msgs);
				miss_pct = (unsigned)(100 *
					info->stats.ins_msgs_miss / info->stats.ins_msgs);
				bad_pct = (unsigned)(100 *
					info->stats.ins_msgs_bad / info->stats.ins_msgs);
				fwd_pct = (unsigned)(100 *
					info->stats.ins_msgs_fwd / info->stats.ins_msgs);
			}
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.ins_msgs);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.hit\">%s</a></td>\n",
				LO("hit"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.ins_msgs_hit, hit_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.bad\">%s</a></td>\n",
				LO("bad"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.ins_msgs_bad, bad_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.miss\">%s</a></td>\n",
				LO("miss"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.ins_msgs_miss, miss_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.fwd\">%s</a></td>\n",
				LO("fwd"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.ins_msgs_fwd, fwd_pct);
			OUT1("      </tr>\n");
		}
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.size\">%s</a></td>\n",
			LO("size"));
		OUT2("       <td class=\"val\">%sB</td>\n",
			round_KMG(info->stats.ins_total));
		OUT1("      </tr>\n");
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#INS.rate\">%s</a></td>\n",
			LO("rate"));
		OUT2("       <td class=\"val\">%sB/m</td>\n",
			round_KMG(info->stats.ins_minute[59]));
		OUT1("      </tr>\n");
		OUT1("     </table>\n");
		OUT1("    </td>\n");
		if (dir == 'o') {
			ins_out_total += info->stats.ins_total;
			ins_out_min_avg += info->stats.ins_minute[59];
		} else {
			ins_in_total += info->stats.ins_total;
			ins_in_min_avg += info->stats.ins_minute[59];
		}

/* FEC */
		OUT1("    <td class=\"right\">\n");
		OUT1("     <table width=100%% cellspacing=1 cellpadding=1>\n");
		if (dir == 'o') {
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.fec_msgs);
			OUT1("      </tr>\n");
		} else {
			hit_pct = miss_pct = bad_pct = fwd_pct = 0;
			if (info->stats.fec_msgs > 0) {
				hit_pct = (unsigned)(100 *
					info->stats.fec_msgs_hit / info->stats.fec_msgs);
				miss_pct = (unsigned)(100 *
					info->stats.fec_msgs_miss / info->stats.fec_msgs);
				bad_pct = (unsigned)(100 *
					info->stats.fec_msgs_bad / info->stats.fec_msgs);
				fwd_pct = (unsigned)(100 *
					info->stats.fec_msgs_fwd / info->stats.fec_msgs);
			}
			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.msgs\">%s</a></td>\n",
				LO("msgs"));
			OUT2("       <td class=\"val\">%llu</td>\n",
				info->stats.fec_msgs);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.hit\">%s</a></td>\n",
				LO("hit"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.fec_msgs_hit, hit_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.bad\">%s</a></td>\n",
				LO("bad"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.fec_msgs_bad, bad_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.miss\">%s</a></td>\n",
				LO("miss"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.fec_msgs_miss, miss_pct);
			OUT1("      </tr>\n");

			OUT1("      <tr>\n");
			OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.fwd\">%s</a></td>\n",
				LO("fwd"));
			OUT3("       <td class=\"val\">%llu (%u%%)</td>\n",
				info->stats.fec_msgs_fwd, fwd_pct);
			OUT1("      </tr>\n");
		}
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.size\">%s</a></td>\n",
			LO("size"));
		OUT2("       <td class=\"val\">%sB</td>\n",
			round_KMG(info->stats.fec_total));
		OUT1("      </tr>\n");
		OUT1("      <tr>\n");
		OUT2("       <td class=\"var\" width=\"20%%\"><a href=\"/node/peers.html?legend=1#FEC.rate\">%s</a></td>\n",
			LO("rate"));
		OUT2("       <td class=\"val\">%sB/m</td>\n",
			round_KMG(info->stats.fec_minute[59]));
		OUT1("      </tr>\n");
		OUT1("     </table>\n");
		OUT1("    </td>\n");
		if (dir == 'o') {
			fec_out_total += info->stats.fec_total;
			fec_out_min_avg += info->stats.fec_minute[59];
		} else {
			fec_in_total += info->stats.fec_total;
			fec_in_min_avg += info->stats.fec_minute[59];
		}
	}

	if (0 == fingerprint_cnt)
		fingerprint_cnt++;
	for (i = 0; i < 16; i++)
		fingerprint[i] = fingerprint_sum[i] / fingerprint_cnt;

/* TOTALS */
	OUT1("   <tr class=\"foot\">\n");
	OUT1("    <td class=\"right\">\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");
	OUT2("      <tr><td class=\"var\" width=\"20%%\" nowrap>%s</td></tr>\n",
		LO("total bytes"));
	OUT2("      <tr class=\"in\"><td class=\"var\" width=\"20%%\" nowrap>%s</td></tr>\n",
		LO("total bytes in"));
	OUT3("      <tr class=\"in\"><td class=\"var\" width=\"20%%\" nowrap>%d %s</td></tr>\n",
		in_conn,
		LO("connected peers in"));
	OUT2("      <tr class=\"in\"><td class=\"var\" width=\"20%%\" nowrap>%s</td></tr>\n",
		LO("avg. data rate in"));
	OUT2("      <tr class=\"out\"><td class=\"var\" width=\"20%%\" nowrap>%s</td></tr>\n",
		LO("total bytes out"));
	OUT3("      <tr class=\"out\"><td class=\"var\" width=\"20%%\" nowrap>%d %s</td></tr>\n",
		out_conn,
		LO("connected peers out"));
	OUT2("      <tr class=\"out\"><td class=\"var\" width=\"20%%\" nowrap>%s</td></tr>\n",
		LO("avg. data rate out"));
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of avg fingerprint */
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of own fingerprint */
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	daily_in_out = info->in_daily + info->out_daily;
	total_in_out = info->in_total + info->out_total;

	OUT1("    <td class=\"right\" nowrap>\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");

	OUT3("      <tr><td class=\"val\" nowrap>%sB (%sB/24h)</td></tr>\n",
		round_KMG(total_in_out),
		round_KMG(daily_in_out));

	OUT3("      <tr class=\"in\"><td class=\"val\" nowrap>%sB (%sB/24h)</td></tr>\n",
		round_KMG(info->in_total),
		round_KMG(info->in_daily));
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(in_total));
	if (g_conf->bwlimit_in > 0)
		pct = (unsigned)(sum_in_min_avg * 100 / 60 / g_conf->bwlimit_in);
	else
		pct = 0;
	OUT3("      <tr class=\"in\"><td class=\"val\" nowrap>%sB/m (%d%%)</td></tr>\n",
		round_KMG(sum_in_min_avg),
		pct);

	OUT3("      <tr class=\"out\"><td class=\"val\" nowrap>%sB (%sB/24h)</td></tr>\n",
		round_KMG(info->out_total),
		round_KMG(info->out_daily));
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(out_total));
	if (g_conf->bwlimit_out > 0)
		pct = (unsigned)(sum_out_min_avg * 100 / 60 / g_conf->bwlimit_out);
	else
		pct = 0;
	OUT3("      <tr class=\"out\"><td class=\"val\" nowrap>%sB/m (%d%%)</td></tr>\n",
		round_KMG(sum_out_min_avg),
		pct);

	OUT6("      <tr><td class=\"right\" nowrap>%s <img src=\"/node/fingerprint%s.png\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"[%s]\"/></td></tr>\n",
		LO("avg"),
		hexstr(fingerprint, 16),
		FPR_W, FPR_H,
		hexstr(fingerprint, 16));
	OUT6("      <tr><td class=\"right\" nowrap>%s <img src=\"/node/fingerprint%s.png\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"[%s]\"/></td></tr>\n",
		LO("own"),
		hexstr(g_store->fingerprint, 16),
		FPR_W, FPR_H,
		hexstr(g_store->fingerprint, 16));
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	OUT1("    <td class=\"right\" width=\"20%%\" nowrap>\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");

	if (g_conf->bwlimit_daily == 0) {
		OUT1("      <tr><td class=\"val\">&nbsp;</td></tr>\n");
	} else if (total_in_out >= g_conf->bwlimit_daily) {
		OUT3("      <tr><td class=\"val\">%s/24h [%s]</td></tr>\n",
			size_KMG(g_conf->bwlimit_daily),
			LO("REACHED"));
	} else {
		OUT3("      <tr><td class=\"val\">%s/24h [%s]</td></tr>\n",
			size_KMG(g_conf->bwlimit_daily),
			LO("not yet"));
	}
	OUT1("      <tr class=\"in\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(adv_in_total));
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(adv_in_min_avg));
	OUT1("      <tr class=\"out\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(adv_out_total));
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(adv_out_min_avg));
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of avg fingerprint */
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of own fingerprint */
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	OUT1("    <td class=\"right\" width=\"20%%\" nowrap>\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");
	OUT1("      <tr><td class=\"val\">&nbsp;</td></tr>\n");	/* total + daily limit */
	OUT1("      <tr class=\"in\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(req_in_total));
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(req_in_min_avg));
	OUT1("      <tr class=\"out\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(req_out_total));
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(req_out_min_avg));
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of avg fingerprint */
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of own fingerprint */
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	OUT1("    <td class=\"right\" width=\"20%%\" nowrap>\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");
	OUT1("      <tr><td class=\"val\">&nbsp;</td></tr>\n");	/* total + daily limit */
	OUT1("      <tr class=\"in\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(ins_in_total));
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(ins_in_min_avg));
	OUT1("      <tr class=\"out\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(ins_out_total));
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(ins_out_min_avg));
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of avg fingerprint */
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of own fingerprint */
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	OUT1("    <td class=\"right\" width=\"20%%\" nowrap>\n");
	OUT1("     <table width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\">\n");
	OUT1("      <tr><td class=\"val\">&nbsp;</td></tr>\n");	/* total + daily limit */
	OUT1("      <tr class=\"in\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(fec_in_total));
	OUT2("      <tr class=\"in\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(fec_in_min_avg));
	OUT1("      <tr class=\"out\"><td class=\"val\">&nbsp;</td></tr>\n");
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB</td></tr>\n",
		round_KMG(fec_out_total));
	OUT2("      <tr class=\"out\"><td class=\"val\" nowrap>%sB/m</td></tr>\n",
		round_KMG(fec_out_min_avg));
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of avg fingerprint */
	OUT1("      <tr><td>&nbsp;</td></tr>\n");	/* row of own fingerprint */
	OUT1("     </table>\n");
	OUT1("    </td>\n");

	OUT1("   </tr>\n");

	OUT1("  </table>\n");
	OUT1("  </div>\n");
	OUT1("  <hr />\n");
	if (0 == reply->legend) {
		OUT4("  <div class=\"title\">%s <a href=\"/node/peers.html?legend=1#legend\">%s</a> %s</div>\n",
			LO("Show the"), LO("legend of variables"), LO(" with their descriptions."));
	} else {
		OUT3("  <div class=\"title\"><a name=\"legend\">%s</a> (<a href=\"?legend=0\">%s</a>)</div>\n",
			LO("legend of variables"), LO("close"));
		OUT1("  <ul>\n");
		OUT3("   <li><b><a name=\"node\">%s:</a></b><br />%s</li>\n",
			LO("node"), LO("A node's address and port. For outbound connections, this is the public port, where the node listens for Entropy connections. For inbound connections the port is not used."));
		OUT3("   <li><b><a name=\"version\">%s:</a></b><br />%s</li>\n",
			LO("version"), LO("Shows how the node identified itself. For outbound connections, Entropy assigns the info as soon as an inbound connection from that same peer is there."));
		OUT3("   <li><b><a name=\"status\">%s:</a></b><br />%s</li>\n",
			LO("status"), LO("Most of the time you will see 'connected' here. You might see 'connecting' here, if the peer is slowly accpeting connections. Unused connection slots (i.e. closed) are not listed, as they don't have useful information."));
		OUT3("   <li><b><a name=\"crypto\">%s:</a></b><br />%s</li>\n",
			LO("crypto"), LO("This is the name of the crypto module used for this connection. The available and supported modules are listed in your configuration file."));
		OUT3("   <li><b><a name=\"last\">%s:</a></b><br />%s</li>\n",
			LO("times"), LO("This variable shows two things: The first time, before the slash, is the time since that connection was established. The second time is the the number of seconds that is gone since the last socket input or output happened. You can see 'stuck peers' if the number of seconds increases to two or even three digit values. Entropy will kick stuck connections every 5 minutes (600 seconds) during the peer node maintenance."));
		OUT3("   <li><b><a name=\"keys\">%s:</a></b><br />%s</li>\n",
			LO("keys"), LO("This shows the number of key received for an incoming connection. It is an indication for the vitality of this peer. The number is the sum of the <a href=\"/node/peers.html?legend=1#ADV\">ADV</a>, <a href=\"/node/peers.html?legend=1#REQ\">REQ</a> and <a href=\"/node/peers.html?legend=1#INS\">INS</a> messages."));
		OUT3("   <li><b><a name=\"queue\">%s:</a></b><br />%s</li>\n",
			LO("queue"), LO("The values show the current and maximum number of pending messages for this outbound connection. The value in parenthesis is the 'queue size'. If the first number is almost always one below the queue size, you see a very vivid outbound connection."));
		OUT1("  </ul>\n");

		OUT1("  <ul>\n");
		OUT3("   <li><b><a name=\"ADV\">%s:</a></b><br />%s</li>\n",
			LO("ADV"), LO("This is short for 'advertized'. The column shows the statistics for keys that were advertized to us (for inbound connections) or that we advertized to some peer node (for outbound connections)."));
		OUT1("   <li style=\"list-style-type:none\">\n");
		OUT1("    <ul>\n");
		OUT3("     <li><b><a name=\"ADV.msgs\">%s:</a></b><br />%s</li>\n",
			LO("msgs"), LO("This shows how many messages were advertized to us (inbound) or from us (outbound)."));
		OUT3("     <li><b><a name=\"ADV.hit\">%s:</a></b><br />%s</li>\n",
			LO("hit"), LO("The number of hits for advertized keys is the number of keys that our node already knew and thus did not request."));
		OUT3("     <li><b><a name=\"ADV.ign\">%s:</a></b><br />%s</li>\n",
			LO("ign"), LO("If key advertizement messages were ignored, they are counted in this value."));
		OUT3("     <li><b><a name=\"ADV.miss\">%s:</a></b><br />%s</li>\n",
			LO("miss"), LO("Misses is the opposite of hits. The percentage of keys, that our node did not know about."));
		OUT3("     <li><b><a name=\"ADV.fwd\">%s:</a></b><br />%s</li>\n",
			LO("fwd"), LO("The percentage of forwarded messages. If our node already had a key, it will tell other nodes about this key, too."));
		OUT3("     <li><b><a name=\"ADV.size\">%s:</a></b><br />%s</li>\n",
			LO("size"), LO("This is the sum of the number of bytes that were sent in either direction, in or out, for advertizements."));
		OUT3("     <li><b><a name=\"ADV.rate\">%s:</a></b><br />%s</li>\n",
			LO("rate"), LO("This tells the data rate in bytes per minute, averaged over the last 60 seconds."));
		OUT1("    </ul>\n");
		OUT1("   </li>\n");

		OUT3("   <li><b><a name=\"REQ\">%s:</a></b><br />%s</li>\n",
			LO("REQ"), LO("This is short for 'requested'. The column shows the statistics for keys that were requested from us (for inbound connections) or that we requested from some peer node (for outbound connections)."));
		OUT1("   <li style=\"list-style-type:none\">\n");
		OUT1("    <ul>\n");
		OUT3("     <li><b><a name=\"REQ.msgs\">%s:</a></b><br />%s</li>\n",
			LO("msgs"), LO("This shows how many messages were requested from us (inbound) or that we requested from a peer (outbound)."));
		OUT3("     <li><b><a name=\"REQ.hit\">%s:</a></b><br />%s</li>\n",
			LO("hit"), LO("The percentage of hits for requests tells how well the peer node knows about what keys we probably have for it."));
		OUT3("     <li><b><a name=\"REQ.ign\">%s:</a></b><br />%s</li>\n",
			LO("ign"), LO("If a request didn't match our data store's fingerprint close enough, it is (sometimes) dropped. This kind of events is counted and the percentage of cases is shown here."));
		OUT3("     <li><b><a name=\"REQ.miss\">%s:</a></b><br />%s</li>\n",
			LO("miss"), LO("Misses is the opposite of hits. It's the percentage of incoming requests for keys, that our node could not satisfy."));
		OUT3("     <li><b><a name=\"REQ.fwd\">%s:</a></b><br />%s</li>\n",
			LO("fwd"), LO("Forwarded requests shows the percentage of incoming requests, that was directed to other peers using our outbound connections. This number can be well over 100%, because a request might be forwarded to more than one node."));
		OUT3("     <li><b><a name=\"REQ.size\">%s:</a></b><br />%s</li>\n",
			LO("size"), LO("This is the sum of the number of bytes that were sent in either direction, in or out, for requests."));
		OUT3("     <li><b><a name=\"REQ.rate\">%s:</a></b><br />%s</li>\n",
			LO("rate"), LO("This tells the data rate in bytes per minute, averaged over the last 60 seconds."));
		OUT1("    </ul>\n");
		OUT1("   </li>\n");

		OUT3("   <li><b><a name=\"INS\">%s:</a></b><br />%s</li>\n",
			LO("INS"), LO("This is short for 'inserted'. The column shows the statistics for keys that were inserted to our store (for inbound connections) or that we inserted to some peer node (for outbound connections)."));
		OUT1("   <li style=\"list-style-type:none\">\n");
		OUT1("    <ul>\n");
		OUT3("     <li><b><a name=\"INS.msgs\">%s:</a></b><br />%s</li>\n",
			LO("msgs"), LO("This shows how many data chunks were sent to us (inbound) or that we sent (outbound)."));
		OUT3("     <li><b><a name=\"INS.hit\">%s:</a></b><br />%s</li>\n",
			LO("hit"), LO("The percentage of hits for inserts tells in how many cases the data that came in was already in our store. This happens if two nodes satisfy previous requests by inserting data, where our node could not cancel one of the requests (because both were already sent)."));
		OUT3("     <li><b><a name=\"INS.bad\">%s:</a></b><br />%s</li>\n",
			LO("bad"), LO("If a broken key comes in, it will be shown here. Only broken peers will insert broken keys or bad data packets, though."));
		OUT3("     <li><b><a name=\"INS.miss\">%s:</a></b><br />%s</li>\n",
			LO("miss"), LO("Misses is the opposite of hits. It's the percentage of incoming inserts for keys and their data chunks, that our node did not already have in its local data store."));
		OUT3("     <li><b><a name=\"INS.fwd\">%s:</a></b><br />%s</li>\n",
			LO("fwd"), LO("Forwarded inserts means the number of inserts that led our node to advertize the key it just received. This number can well be over 100%, because an insert will be forwarded, depending on its 'hops to live' value and the number of free outbound queue entries."));
		OUT3("     <li><b><a name=\"INS.size\">%s:</a></b><br />%s</li>\n",
			LO("size"), LO("This is the sum of the number of bytes that were sent in either direction, in or out, for data. This message type includes the actual data 'chunks', so you will usually see the highest amount of traffic listed in the ADV column."));
		OUT3("     <li><b><a name=\"INS.rate\">%s:</a></b><br />%s</li>\n",
			LO("rate"), LO("This tells the data rate in bytes per minute, averaged over the last 60 seconds."));
		OUT1("    </ul>\n");
		OUT1("   </li>\n");

		OUT1("  </ul>\n");
	}
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_node_store()
 *	Collect and print some store status information.
 */
static int proxy_node_store(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	size_t i, avg, dev, lim, maxkeys;
	size_t fpr_sum, dst_sum;
#if	STORE_TYPE == 0
	size_t totseeks;
#endif
	store_info_t *info = (store_info_t *)xcalloc(1, sizeof(store_info_t));
	int fd = -1;
	int rc = 0;
	FUN("proxy_node_store");

	(void)url;

	if (0 != (rc = store_info(info))) {
		LOGS(L_PROXY,L_ERROR,("store_info() call failed!\n"));
		goto bailout;
	}

	dst = msg = xcalloc(max, sizeof(char));
	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT3("  <title>%s %s</title>\n",
		VERSION,
		LO("Store Status"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("Store Status")));
	OUT1("  <div align=\"center\">\n");
	OUT1("   <table>\n");
	OUT1("    <tr>\n");
	OUT2("     <th>%s</th>\n",
		LO("Overview"));
	OUT1("    </tr>\n");
	OUT1("    <tr>\n");
	OUT1("     <td colspan=\"2\">\n");
	OUT1("      <table width=100%% cellspacing=1 cellpadding=1>\n");
	OUT1("       <tr class=\"head\">\n");
	OUT3("        <th class=\"var\" width=\"50%%\">%s %s</th>\n",
		LO("Data store"),
		LO("maximum"));
	OUT3("        <td class=\"val\">%llu %s</td>\n",
		info->storesize,
		LO("bytes"));
	OUT2("        <td class=\"val\">%s</td>\n",
		round_KMG(info->storesize));
	OUT1("       </tr>\n");
	OUT1("       <tr class=\"head\">\n");
	OUT3("        <th class=\"var\" width=\"50%%\">%s %s</th>\n",
		LO("Data store"),
		LO("current"));
	OUT3("        <td class=\"val\">%llu %s</td>\n",
		info->currsize,
		LO("bytes"));
	OUT2("        <td class=\"val\">%s</td>\n",
		round_KMG(info->currsize));
	OUT1("       </tr>\n");
	OUT1("       <tr class=\"head\">\n");
	OUT3("        <th class=\"var\" width=\"50%%\">%s %s</th>\n",
		LO("Key store"),
		LO("current"));
	OUT3("        <td class=\"val\">%u %s</td>\n",
		info->storecount,
		LO("keys"));
	OUT2("        <td class=\"val\">%s</td>\n",
		round_KMG(info->storecount));
	OUT1("       </tr>\n");
	OUT1("      </table>\n");
	OUT1("     </td>\n");
	OUT1("    </tr>\n");
	OUT1("    <tr>\n");
	OUT2("     <th>%s</th>\n",
		LO("Key counts"));
#if	STORE_TYPE == 0
	OUT2("     <th>%s</th>\n",
		LO("Seek distances"));
#endif
	OUT1("    </tr>\n");
	OUT1("    <tr>\n");
	OUT1("     <td class=\"center\">\n");
	OUT1("      <table width=100%% cellspacing=1 cellpadding=1>\n");
	for (i = 0, maxkeys = 1, avg = 0; i < 16; i++) {
		if (info->keycount[i] > maxkeys)
			maxkeys = info->keycount[i];
		avg += info->keycount[i];
	}
	avg /= 16;
	for (i = 0, dev = 0; i < 16; i++) {
		dev += info->keycount[i] >= avg ?
			info->keycount[i] - avg : avg - info->keycount[i];
	}
	OUT1("       <tr class=\"head\">\n");
	OUT2("        <th>%s</th>\n",
		LO("routing"));
	OUT2("        <th>%s</th>\n",
		LO("count"));
	OUT2("        <th>%s</th>\n",
		LO("relative"));
	OUT2("        <th>%s</th>\n",
		LO("dst"));
	OUT2("        <th>%s</th>\n",
		LO("fpr"));
	OUT1("       </tr>\n");
	for (i = 0, fpr_sum = 0,  dst_sum = 0; i < 16; i++) {
		OUT2("       <tr class=\"%s\">\n",
			(i & 1) ? "odd" : "even");
		OUT2("        <td class=\"var\" width=\"50%%\">%X</td>\n",
			i);
		OUT2("        <td class=\"val\">%d</td>\n",
			info->keycount[i]);
		OUT2("        <td class=\"val\">%d%%</td>\n",
			(int)((uint64_t)100 * info->keycount[i] / maxkeys));
		OUT2("        <td class=\"val\">%02X</td>\n",
			g_store->destination[i]);
		OUT2("        <td class=\"val\">%02X</td>\n",
			g_store->fingerprint[i]);
		OUT1("       </tr>\n");
		dst_sum += g_store->destination[i];
		fpr_sum += g_store->fingerprint[i];
	}
	OUT2("       <tr class=\"%s\">\n",
		(i & 1) ? "odd" : "even");
	OUT3("        <td class=\"var\" width=\"20%%\">%s, %s</td>\n",
		LO("average"), LO("limit"));
	OUT2("        <td class=\"val\">%u</td>\n",
		(unsigned)avg);
	lim = (unsigned)((int)(avg - dev/LIMIT_FRACT) < 0 ?
		avg*(LIMIT_FRACT-1)/LIMIT_FRACT : avg - dev/LIMIT_FRACT);
	OUT2("        <td class=\"val\">%u</td>\n",
		lim);
	OUT2("        <td class=\"val\">%02X</td>\n",
		dst_sum / 16);
	OUT2("        <td class=\"val\">%02X</td>\n",
		fpr_sum / 16);
	OUT1("       </tr>\n");
	OUT1("      </table>\n");
	OUT1("     </td>\n");
#if	STORE_TYPE == 0
	OUT1("     <td class=\"center\">\n");
	OUT1("      <table width=100%% cellspacing=1 cellpadding=1>\n");
	OUT1("       <tr class=\"head\">\n");
	OUT2("        <th>%s</th>\n",
		LO("distance"));
	OUT2("        <th>%s</th>\n",
		LO("count"));
	OUT2("        <th>%s</th>\n",
		LO("relative"));
	for (i = 0, totseeks = 0; i < SEEK_SLOTS; i++)
		totseeks += info->seeks[i];
	if (0 == totseeks)
		totseeks = 1;
	for (i = 0; i < SEEK_SLOTS; i++) {
		OUT2("       <tr class=\"%s\">\n",
			(i & 1) ? "odd" : "even");
		OUT3("        <td class=\"var\" width=\"50%%\">%d - %d</td>\n",
			(1<<i), (1<<(i+1))-1);
		OUT2("        <td class=\"val\">%d</td>\n",
			info->seeks[i]);
		OUT2("        <td class=\"val\">%d%%</td>\n",
			(int)((uint64_t)100 * info->seeks[i] / totseeks));
		OUT1("       </tr>\n");
	}
	OUT2("       <tr class=\"%s\">\n",
		(i & 1) ? "odd" : "even");
	OUT2("        <td class=\"var\" width=\"20%%\">%s</td>\n",
		LO("overwrites"));
	OUT2("        <td class=\"val\">%d</td>\n",
		info->seeks[i]);
	OUT2("        <td class=\"val\">%d%%</td>\n",
		(int)((uint64_t)100 * info->seeks[i] / totseeks));
	OUT1("       </tr>\n");
	OUT1("      </table>\n");
	OUT1("     </td>\n");
#endif
	OUT1("    </tr>\n");
	OUT1("   </table>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

bailout:
	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	xfree(msg);
	xfree(info);
	return rc;
}

static int proxy_gateway(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	char *gateway = xcalloc(MAXPATHLEN, sizeof(char));
	char *html = NULL;
	struct stat st;
	int fd = -1;
	int rc;
	FUN("proxy_gateway");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	pm_snprintf(gateway, MAXPATHLEN, "%s/node/gateway.%s.html",
		g_conf->configpath, reply->language);

	if (0 == stat(gateway, &st) && S_ISREG(st.st_mode)) {

		fd = open(gateway, O_RDONLY|O_BINARY);

		html = xcalloc(st.st_size + 1, sizeof(char));

		if (st.st_size != read(fd, html, st.st_size)) {
			xfree(html);
			html = xstrdup(
				DOCTYPE \
				"<html>\n" \
				" <head>\n" \
				"  <title>%s Gateway</title>\n" \
				CONTENT_TYPE \
				STYLESHEET \
				" </head>\n" \
				" <body>\n" \
				"  <div class=\"title\">" PROGNAME " Gateway</div>\n" \
        		"  <p>There was a problem reading the gateway.??.html in\n" \
				"  your " PROGNAME " directory.</p>\n" \
				" </body>\n" \
				"</html>\n");
		}

	} else {
		xfree(html);
		reply->user = html = xstrdup(
			DOCTYPE \
			"<html>\n" \
			" <head>\n" \
			"  <title>%s Gateway</title>\n" \
			CONTENT_TYPE \
			STYLESHEET \
			" </head>\n" \
			" <body>\n" \
			"  <div class=\"title\">" PROGNAME " Gateway</div>\n" \
        	"  <p>There was no gateway.??.html file found\n" \
			"  in your " PROGNAME " directory.</p>\n" \
			" </body>\n" \
			"</html>\n");
		reply->content_length = strlen(reply->user);
	}

	if (-1 != fd) {
		close(fd);
		fd = -1;
	}

	html_vars(reply, &html);
	rc = proxy_reply(conn);
	
	xfree(html);
	xfree(gateway);

	return rc;
}

static int proxy_image_ext(conn_t *conn, const char *path, const char *file)
{
	proxy_reply_t *reply = conn->temp;
	size_t len, size = strlen(path) + 1 + strlen(file) + 1;
	char *filename = xcalloc(size, sizeof(char));
	char *ext;
	struct stat st;
	int fd, rc = 0;
	FUN("proxy_image_ext");

	reply->result_code = 200;
	len = pm_snprintf(filename, size, "%s/%s", path, file);
	ext = filename + len - 4;
	xfree(reply->content_type);
	if (0 == strcasecmp(ext, ".gif")) {
		reply->content_type = xstrdup("image/gif");
	} else if (0 == strcasecmp(ext, ".png")) {
		reply->content_type = xstrdup("image/png");
	} else if (0 == strcasecmp(ext, ".jpg")) {
		reply->content_type = xstrdup("image/jpg");
	} else if (0 == strcasecmp(ext, ".bmp")) {
		reply->content_type = xstrdup("image/bmp");
	} else if (0 == strcasecmp(ext, ".ico")) {
		reply->content_type = xstrdup("image/bmp");
	} else if (0 == strcasecmp(ext, ".txt")) {
		reply->content_type = xstrdup("text/plain");
	} else if (0 == strcasecmp(ext, ".htm")) {
		reply->content_type = xstrdup("text/html");
	} else if (0 == strcasecmp(ext, ".html")) {
		reply->content_type = xstrdup("text/html");
	} else {
		reply->content_type = xstrdup("application/octet-stream");
	}
	if (0 != stat(filename, &st) ||
		-1 == (fd = open(filename, O_RDONLY|O_BINARY))) {
		reply->content_type = xstrdup("text/plain");
		reply->content_length = 0;
		reply->user = "cannot open file";
		reply->passthru = pass_str;
	} else {
		reply->content_length = st.st_size;
		reply->user = &fd;
		reply->passthru = pass_fd;
	}
	xfree(filename);

	rc = proxy_reply(conn);

	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	
	return rc;
}

static int proxy_loadgraphics_png(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	tempfile_t tfpng;
	char pct[4+1];
	char *filename = NULL;
	int estimated_load = 0;
	png_t *png = NULL;
	int fd = -1, rc = 0;
	FUN("proxy_loadgraphics_png");

	url += strlen("node/loadgraphics");
	estimated_load = strtol(url, NULL, 16);
	if (estimated_load < 0) {
		estimated_load = 0;
	} else if (estimated_load > 100) {
		estimated_load = 100;
	}

	if (0 != (rc = temp_binary(&tfpng, "loadgraphics", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}
	filename = xcalloc(MAXPATHLEN, sizeof(char));
	pm_snprintf(filename, MAXPATHLEN, "%s/node/bar.png",
		g_conf->configpath);
	if (NULL == (png = png_read(filename, &tfpng, write_tf))) {
		LOGS(L_PROXY,L_ERROR,("png_read('%s') call failed!\n",
			filename));
		goto bailout;
	}
	png_tinted_rectangle(png, 0, 0, estimated_load, 16,
		PNG_RGB(255,0,0), 255);
	png_tinted_rectangle(png, estimated_load + 1, 1, 100-1, 16-1,
		PNG_RGB(64,192,255), 255);
	pm_snprintf(pct, sizeof(pct), "%d%%",
		estimated_load);
	png_printf(png, 2 + (100 - strlen(pct) * 8) / 2, 1, PNG_RGB(0,0,0), 255,
		"%d%%", estimated_load);
	png_finish(png);
	temp_close(&tfpng, 0);

	if (-1 == (fd = temp_open(&tfpng))) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("image/png");
	reply->content_length = tfpng.size;
	reply->user = &tfpng;
	reply->passthru = pass_tf;
	rc = proxy_reply(conn);

bailout:
	xfree(filename);
	temp_close(&tfpng, 1);
	return rc;
}

static int proxy_latestbuild_png(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	tempfile_t tfpng;
	char *tmp_ver = NULL;
	char *filename = NULL;
	png_t *png = NULL;
	int len, x, fd = -1, rc = 0;
	FUN("proxy_latestbuild_png");

	if (-1 == (fd = temp_binary(&tfpng, "latestbuild", TEMP_UNKNOWN_SIZE))) {
		LOGS(L_PROXY,L_ERROR,("temp_binary('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}
	filename = xcalloc(MAXPATHLEN, sizeof(char));
	pm_snprintf(filename, MAXPATHLEN, "%s/node/bar.png",
		g_conf->configpath);
	if (NULL == (png = png_read(filename, &tfpng, write_tf))) {
		LOGS(L_PROXY,L_ERROR,("png_read('%s') call failed!\n",
			filename));
		goto bailout;
	}
	tmp_ver = xcalloc(32, sizeof(char));
	len = pm_snprintf(tmp_ver, 32, "%u.%u.%u-%u",
		g_conf->latest_major,
		g_conf->latest_minor / 65536,
		g_conf->latest_minor % 65536,
		g_conf->latest_build);
	x = (100 - len * 8) / 2;
	if (g_conf->latest_build > strtoul(NODE_BUILD, NULL, 0)) {
		/* tint with red */
		png_tinted_rectangle(png, 0, 0, 100-1, 16-1, PNG_RGB(255,192,0), 255);
		png_printf(png, x+0, 1, PNG_RGB(0,0,0), 255, tmp_ver);
		png_printf(png, x+1, 1, PNG_RGB(0,0,0), 255, tmp_ver);
	} else {
		png_printf(png, x, 1, PNG_RGB(0,0,0), 255, tmp_ver);
	}

	png_finish(png);
	temp_close(&tfpng, 0);

	if (-1 == (fd = temp_open(&tfpng))) {
		LOGS(L_PROXY,L_ERROR,("temp_open('%s') call failed (%s)\n",
			tfpng.name, strerror(errno)));
		goto bailout;
	}

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("image/png");
	reply->content_length = tfpng.size;
	reply->user = &tfpng;
	reply->passthru = pass_tf;
	rc = proxy_reply(conn);

bailout:
	xfree(filename);
	temp_close(&tfpng, 1);
	return rc;
}

static int proxy_vt_news_post_gif(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	pid_t pid;
	vt_t *vt = NULL;
	char *buff = NULL;
	int fds[2];
	int done, rc;
	FUN("proxy_vt_news_post_gif");

	if (0 == svalid(reply->news_post) &&
		RESULT_CREATED == reply->news_post->rc) {
		news_post_t *np = reply->news_post;

		LOGS(L_PROXY,L_MINOR,("found news_post=%p\n", np));

		np->rc = RESULT_PENDING;

		if (0 != (rc = pipe(fds))) {
			LOGS(L_PROXY,L_ERROR,("pipe() call failed (%s)\n",
				strerror(errno)));
			np->rc = RESULT_ERROR;
			return -1;
		}

		reply->result_code = 200;
		reply->no_cache = 1;
		xfree(reply->content_type);
		reply->content_type = xstrdup("image/gif");
		reply->content_length = UNKNOWN_LENGTH;
		reply->user = NULL;
		reply->passthru = NULL;

		if (0 != (rc = proxy_reply(conn))) {
			LOGS(L_PROXY,L_ERROR,("proxy_reply() returned %d\n",
				rc));
			np->rc = RESULT_ERROR;
			return -1;
		}
		LOGS(L_PROXY,L_MINOR,("sent GIF header; going to fork()\n"));

		switch (pid = fork()) {
		case -1:
			np->rc = rc = RESULT_ERROR;
			close(fds[0]);
			close(fds[1]);
			osd_exit(rc);
		case 0:
			/* the child reads the pipe at fds[0] and sends to the socket */
			LOGS(L_PROXY,L_MINOR,("start GIF pipe fd:%d to sock:%d\n",
				fds[0], conn->socket));
			close(fds[1]);
			buff = xcalloc(LINESIZE, sizeof(char));
			while ((done = read(fds[0], buff, LINESIZE)) > 0) {
				if (0 != (rc = sock_writeall(conn, buff, done))) {
					LOGS(L_PROXY,L_ERROR,("failed to write %d bytes\n", done));
					break;
				}
			}
			LOGS(L_PROXY,L_MINOR,("end of GIF pipe\n"));
			xfree(buff);
			close(fds[0]);
			osd_exit(rc);
		default:
			LOGS(L_PROXY,L_MINOR,("fork() parent of {%d}\n", (int)pid));
			/* the parent closes the socket and writes to the pipe at fds[1] */
			close(fds[0]);
			close(1);
			dup2(fds[1], 1);
			close(conn->socket);
			if (NULL == (vt = vt_create(80, 24))) {
				LOGS(L_PROXY,L_ERROR,("vt_create(%d,%d,%d) call failed (%s)\n",
					fds[1], 80, 24, strerror(errno)));
				np->rc = RESULT_ERROR;
				close(fds[1]);
				return -1;
			}
			vt_printf(vt, "%s\n\n\n\n\t\t--- %s ---\n\n%s\n",
				CLRSCR,
				LO("Insert message starting."),
				LO("If this message does not change, you need to enable image animation in your Web browser to see the insert progress."));
			vt_printf(vt, "%s%s\t%s %s\n",
				CLRSCR, CAPTION,
				LO("Inserting message in progress."),
				LO("Please wait for the result."));
			vt_printf(vt, "%s%s%s\n",
				REGION, HILITE, LO("News post"));
			np->user = vt;
			np->writer = vt_write;
			rc = news_post(LANG, np);
			LOGS(L_PROXY,L_MINOR,("news_post() returned %d\n", rc));
			if (0 == rc) {
				vt_printf(vt, "\n%s\t\t\t%s%s\n",
						GREEN, LO("MESSAGE UPLOAD SUCCESSFUL"), HILITE);
			} else {
				vt_printf(vt, "\n%s\t\t\t%s%s\n",
						RED, LO("MESSAGE UPLOAD FAILED"), HILITE);
			}
			vt_printf(vt, "%s", CLICK);
			np->rc = rc;
			np->user = NULL;
			np->writer = NULL;
			vt_destroy(vt);
			vt = NULL;
			/* wait until the child exits */
			waitpid(pid, &done, WNOHANG);
			break;
		}

		return 0;
	}

	LOGS(L_PROXY,L_MINOR,("news_post is valid\n"));
	rc = proxy_image_ext(conn, g_conf->configpath,
		"node/logo_small.png");

	return 0;
}


static int proxy_vt_file_up_gif(conn_t *conn)
{
	proxy_reply_t *reply = conn->temp;
	pid_t pid;
	vt_t *vt;
	FILE *fp = NULL;
	conn_t *fcp = NULL;
	char *line = NULL;
	char *data = NULL, *dst;
	char *meta = NULL;
	char *percent = NULL;
	size_t fcp_blocksize, offs, size;
	uint64_t offset = 0, datalength = 0;
	char *buff = NULL;
	int fds[2];
	int done, rc;
	int state, endmessage;
	FUN("proxy_vt_file_up_gif");

	if (0 == svalid(reply->file_up) &&
		RESULT_CREATED == reply->file_up->rc) {
		file_up_t *up = reply->file_up;

		up->rc = RESULT_PENDING;

		if (0 != (rc = pipe(fds))) {
			LOGS(L_PROXY,L_ERROR,("pipe() call failed (%s)\n",
				strerror(errno)));
			return -1;
		}

		reply->result_code = 200;
		reply->no_cache = 1;
		xfree(reply->content_type);
		reply->content_type = xstrdup("image/gif");
		reply->content_length = UNKNOWN_LENGTH;
		reply->user = NULL;
		reply->passthru = NULL;

		if (0 != (rc = proxy_reply(conn))) {
			LOGS(L_PROXY,L_ERROR,("proxy_reply() returned %d\n",
				rc));
			return -1;
		}
		LOGS(L_PROXY,L_MINOR,("sent GIF header; going to fork()\n"));

		switch (pid = fork()) {
		case -1:
			LOGS(L_PROXY,L_ERROR,("fork() failed (%s)\n",
				strerror(errno)));
			up->rc = rc = RESULT_ERROR;
			close(fds[0]);
			close(fds[1]);
			osd_exit(rc);
		case 0:
			/* the child reads the pipe at fds[0] and sends to the socket */
			LOGS(L_PROXY,L_MINOR,("start GIF pipe fd:%d to sock:%d\n",
				fds[0], conn->socket));
			close(fds[1]);
			buff = xcalloc(LINESIZE, sizeof(char));
			while ((done = read(fds[0], buff, LINESIZE)) > 0) {
				if (0 != (rc = sock_writeall(conn, buff, done))) {
					LOGS(L_PROXY,L_ERROR,("failed to write %d bytes\n", done));
					break;
				}
			}
			LOGS(L_PROXY,L_MINOR,("end of GIF pipe\n"));
			xfree(buff);
			close(fds[0]);
			osd_exit(rc);
			break;
		default:
			LOGS(L_PROXY,L_MINOR,("fork() parent of {%d}\n", (int)pid));
			/* the parent closes the socket and writes to the pipe at fds[1] */
			close(fds[0]);
			close(1);
			dup2(fds[1], 1);
			close(conn->socket);
			if (NULL == (vt = vt_create(80, 24))) {
				LOGS(L_PROXY,L_ERROR,("vt_create(%d,%d,%d) call failed (%s)\n",
					fds[1], 80, 24, strerror(errno)));
				up->rc = RESULT_ERROR;
				close(fds[1]);
				goto insert_bail;
			}
			vt_printf(vt, "%s\n\n\n\n\t\t--- %s ---\n\n%s\n",
				CLRSCR,
				LO("Insert file starting."),
				LO("If this message does not change, you need to enable image animation in your Web browser to see the insert progress."));
			vt_printf(vt, "%s%s\t%s %s\n",
				CLRSCR, CAPTION,
				LO("Inserting data in progress."),
				LO("Please wait for the result."));
			vt_printf(vt, "%s%s%s\n",
				REGION, HILITE, LO("File insert"));

			fp = fopen(up->tmpfile, "rb");
			if (NULL == fp) {
				LOGS(L_PROXY,L_ERROR,("fopen('%s','%s') call failed (%s)\n",
					up->tmpfile, "rb", strerror(errno)));
				goto insert_bail;
			}

			fcp = (conn_t *)xcalloc(1, sizeof(conn_t));

			rc = sock_outgoing(fcp, g_conf->fcphost, g_conf->fcpport, 0);
			if (0 != rc) {
				LOGS(L_PROXY,L_ERROR,("could not connect to %s:%d\n",
					g_conf->fcphost, g_conf->fcpport));
				vt_printf(vt, "could not connect to %s:%d\n",
					g_conf->fcphost, g_conf->fcpport);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s:%d%s\n",
				LO("connected to FCP server"),
				GREEN, g_conf->fcphost, g_conf->fcpport, HILITE);
			LOGS(L_PROXY,L_MINOR,("connected to FCP server %s:%d\n",
				g_conf->fcphost, g_conf->fcpport));

			meta = xcalloc(LINESIZE, sizeof(char));
			dst = meta;
			dst += pm_spprintf(dst,meta,LINESIZE,"Version\n");
			dst += pm_spprintf(dst,meta,LINESIZE,"Revision=1\n");
			dst += pm_spprintf(dst,meta,LINESIZE,"EndPart\n");
			dst += pm_spprintf(dst,meta,LINESIZE,"Document\n");
			dst += pm_spprintf(dst,meta,LINESIZE,"Info.Format=%s\n",
				up->content_type);
			dst += pm_spprintf(dst,meta,LINESIZE,"Info.Description=%s\n",
				"file");
			dst += pm_spprintf(dst,meta,LINESIZE,"End\n");
			up->metasize = (size_t)(dst - meta);
	
			vt_printf(vt, "%s\n%s%s%s",
				LO("document meta data:"), BLUE, meta, HILITE);
			if (0 != (rc = sock_writeall(fcp, "\0\0\1\2", 4))) {
				LOGS(L_PROXY,L_ERROR,("failed to send FCP header\n"));
				vt_printf(vt, "%s %s\n",
					LO("failed to send"), "[00] [00] [01] [02]");
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s%s\n",
				LO("sent"), BROWN, "[00] [00] [01] [02]", HILITE);
			if (0 != (rc = sock_printf(fcp, "ClientPut\n"))) {
				LOGS(L_PROXY,L_ERROR,("failed to send 'ClientPut'\n"));
				vt_printf(vt, "%s %s%s%s\n",
					LO("failed to send"), RED, "ClientPut", HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s%s\n",
				LO("sent"), BROWN, "ClientPut", HILITE);
			if (0 != (rc = sock_printf(fcp, "URI=%s\n", up->key))) {
				LOGS(L_PROXY,L_ERROR,("failed to send 'URI=%s'\n", up->key));
				vt_printf(vt, "%s '%s=%s'\n",
					LO("failed to send"), "URI", up->key);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s=%s%s\n",
				LO("sent"), BROWN, "URI", up->key, HILITE);
			if (0 != (rc = sock_printf(fcp, "HopsToLive=%x\n", up->htl))) {
				LOGS(L_PROXY,L_ERROR,("failed to send HopsToLive=%x\n", up->htl));
				vt_printf(vt, "%s %s%s=%x%s\n",
					LO("failed to send"), RED, "HopsToLive", up->htl, HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s=%x%s\n",
				LO("sent"), BROWN, "HopsToLive", up->htl, HILITE);
			size = up->datasize + up->metasize;
			if (0 != (rc = sock_printf(fcp, "DataLength=%x\n", size))) {
				LOGS(L_PROXY,L_ERROR,("failed to send DataLength=%x\n", size));
				vt_printf(vt, "%s %s%s=%x%s\n",
					LO("failed to send"), RED, "DataLength", size, HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s=%x%s\n",
				LO("sent"), BROWN, "DataLength", (unsigned)size, HILITE);
			size = up->metasize;
			if (0 != (rc = sock_printf(fcp, "MetadataLength=%x\n", (unsigned)size))) {
				LOGS(L_PROXY,L_ERROR,("failed to send MetadataLength=%x\n", size));
				vt_printf(vt, "%s %s%s=%x%s\n",
					LO("failed to send"), RED, "MetadataLength", (unsigned)size, HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s=%x%s\n",
				LO("sent"), BROWN, "MetadataLength", size, HILITE);
			if (0 != (rc = sock_printf(fcp, "Verbose=%d\n", 1))) {
				LOGS(L_PROXY,L_ERROR,("failed to send Verbose=%d\n", 1));
				vt_printf(vt, "%s %s%s=%d%s\n",
					LO("failed to send"), RED, "Verbose", 1, HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s=%d%s\n",
				LO("sent"), BROWN, "Verbose", 1, HILITE);
			if (0 != (rc = sock_printf(fcp, "Data\n"))) {
				LOGS(L_PROXY,L_ERROR,("failed to send Data\n"));
				vt_printf(vt, "%s %s%s%s\n",
					LO("failed to send"), RED, "Data", HILITE);
				goto insert_bail;
			}
			vt_printf(vt, "%s %s%s%s\n",
				LO("sent"), BROWN, "Data", HILITE);
			if (0 != (rc = sock_writeall(fcp, meta, up->metasize))) {
				LOGS(L_PROXY,L_ERROR,("failed to send [metadata]\n"));
				vt_printf(vt, "%s %s%s%s\n",
					LO("failed to send"), RED, "[metadata]", HILITE);
				goto insert_bail;
			}
	
			fcp_blocksize = (fcp->maxsize < FCP_BLOCKSIZE) ?
				fcp->maxsize : FCP_BLOCKSIZE;
			data = xcalloc(fcp_blocksize, sizeof(char));
			vt_printf(vt, "%s %s %s %s %s %s.\n",
				LO("sending"),
				round_KMG(up->datasize),
				LO("bytes"),
				LO("data in chunks of"),
				round_KMG(fcp_blocksize),
				LO("bytes"));

			/* seek to where the data begins in the file */
			fseek(fp, up->dataoffs, SEEK_SET);
	
			size = up->datasize;
			for (offs = 0; offs < size; /* */) {
				size_t copy = (size - offs) > fcp_blocksize ?
					fcp_blocksize : size - offs;
				if (copy != fread(data, 1, copy, fp)) {
					LOGS(L_PROXY,L_ERROR,("could not read [data]\n"));
					vt_printf(vt, "%s %s%s%s\n",
						LO("failed to read"), RED, "[data]", HILITE);
					goto insert_bail;
				}
				if (0 != (rc = sock_writeall(fcp, data, copy))) {
					LOGS(L_PROXY,L_ERROR,("failed to send [data]\n"));
					vt_printf(vt, "%s %s%s%s\n",
						LO("failed to send"), RED, "[data]", HILITE);
					goto insert_bail;
				}
				vt_printf(vt, "%s %s %s %s %s (%d%%)\r",
					LO("sent"),
					round_KMG(copy),
					LO("bytes"),
					LO("at offset"),
					round_KMG(offs),
					(int)((uint64_t)100 * (offs + copy) / size));
				offs += copy;
			}

			vt_printf(vt, "\n%s\n",
				LO("finished sending data"));

			vt_printf(vt, "%s\n",
				LO("waiting for node replies"));

			state = INSERT_PENDING;
			endmessage = 0;
			while (0 == (rc = sock_agets(fcp, &line))) {
				char *var, *val;
				LOGS(L_PROXY,L_MINOR,("fcp: %s\n", line));
				if (0 == strncmp(line, "Success", 7)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), GREEN, line, HILITE);
					state = INSERT_SUCCESS;
					endmessage = 0;
				} else if (0 == strncmp(line, "KeyCollision", 12)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), BROWN, line, HILITE);
					state = INSERT_COLLISION;
					endmessage = 0;
				} else if (0 == strncmp(line, "RouteNotFound", 13)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), RED, line, HILITE);
					state = INSERT_RNF;
					endmessage = 0;
				} else if (0 == strncmp(line, "DataNotFound", 12)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), RED, line, HILITE);
					state = INSERT_DNF;
					endmessage = 0;
				} else if (0 == strncmp(line, "URIError", 8)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), RED, line, HILITE);
					state = INSERT_URI;
					endmessage = 0;
				} else if (0 == strncmp(line, "Restarted", 9)) {
					vt_printf(vt, "\n%s %s%s%s",
						LO("received"), RED, line, HILITE);
					state = INSERT_PENDING;
					endmessage = 0;
				} else if (0 == strncmp(line, "NodePut", 7)) {
					state = INSERT_NODEPUT;
					endmessage = 0;
				} else if (0 == strncmp(line, "EndMessage", 10)) {
					endmessage = 1;
				}
				var = strtok(line, "=\r\n");
				val = strtok(NULL, "\r\n");
				if (NULL != var) {
					if (0 == strcasecmp(var, "URI")) {
						strncpy(up->fquri, val, sizeof(up->fquri));
					} else if (0 == strcasecmp(var, "Offset")) {
						offset = strtoul(val, NULL, 16);
					} else if (0 == strcasecmp(var, "DataLength")) {
						datalength = strtoul(val, NULL, 16);
					} else if (0 == strcasecmp(line, "Percent")) {
						xfree(percent);
						percent = xstrdup(val);
						vt_printf(vt, "%s %s %s %s %s (%s)        \r",
							LO("inserted"), round_KMG(offset), LO("of"),
							round_KMG(datalength), LO("bytes"), percent);
					}
				}
				xfree(line);
				if ((INSERT_PENDING != state && INSERT_NODEPUT != state) &&
					0 != endmessage)
					break;
			}
			rc = (INSERT_SUCCESS == state || INSERT_COLLISION == state) ?
				0 : -1;
			LOGS(L_PROXY,L_MINOR,("******** FILE UP DONE; rc:%d\n", rc));
			if (0 == rc) {
				vt_printf(vt, "\n%s\t\t\t%s%s\n",
					GREEN, LO("FILE UPLOAD SUCCESSFUL"), HILITE);
			} else {
				vt_printf(vt, "\n%s\t\t\t%s%s\n",
					RED, LO("FILE UPLOAD FAILED"), HILITE);
			}
insert_bail:
			vt_printf(vt, "%s", CLICK);
			sock_shutdown(fcp);
			xfree(fcp);
			xfree(line);
			xfree(data);
			xfree(percent);
			if (NULL != fp) {
				LOGS(L_PROXY,L_MINOR,("closing tmpfile '%s'\n",
					up->tmpfile));
				fclose(fp);
				fp = NULL;
			}
			if (up->tmpfile[0] != '\0') {
				LOGS(L_PROXY,L_MINOR,("removing tmpfile '%s'\n",
					up->tmpfile));
				unlink(up->tmpfile);
				up->tmpfile[0] = '\0';
			}
			up->rc = rc;
			vt_destroy(vt);
			vt = NULL;
			/* wait until the child exits */
			waitpid(pid, &done, WNOHANG);
			break;
		}

		return 0;
	}

	LOGS(L_PROXY,L_MINOR,("file_up is valid\n"));
	rc = proxy_image_ext(conn, g_conf->configpath,
		"node/logo_small.png");

	return 0;
}

static int proxy_chat(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	char *chat = xcalloc(MAXPATHLEN, sizeof(char));
	char *html = NULL;
	struct stat st;
	int fd = -1;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc = 0;
	FUN("proxy_chat");

	xfree(reply->charset);
	reply->charset = xstrdup("utf-8");

	if (0 == strcmp(url, "chat") ||
		0 == strcmp(url, "chat/") ||
		0 == strcmp(url, "chat/index.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/index.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/title.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/title.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/topright.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/topright.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/filler.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/filler.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/status.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/status.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/output.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/output.html",
			g_conf->configpath);
	} else if (0 == strcmp(url, "chat/input.html")) {
		snprintf(chat, MAXPATHLEN, "%s/chat/input.html",
			g_conf->configpath);
		if (NULL != reply->chat_sender) {
			snprintf(chat, MAXPATHLEN, "%s/chat/input_next.html",
				g_conf->configpath);
			if (NULL != reply->chat_payload) {
				char chat_sender_color[MAXSENDER+32];
				pm_snprintf(chat_sender_color, sizeof(chat_sender_color),
					"%s#%06x", reply->chat_sender,
					(unsigned)(reply->chat_color & 0xffffff));
				rc = peer_ins_broadcast(
					reply->chat_channel,
					chat_sender_color,
					reply->chat_payload,
					g_conf->maxhtl / 3);
			}
		}
	} else {
		dst = msg = xcalloc(max, sizeof(char));

		OUT1(DOCTYPE);
		OUT1("<html>\n");
		OUT1(" <head>\n");
		OUT4("  <title>%s %s (%s)</title>\n",
			VERSION,
			LO("status"),
			url);
		OUT1(CONTENT_TYPE);
		OUT1(STYLESHEET);
		OUT1(" </head>\n");
		OUT1(" <body>\n");
		OUT2(" <p>Wrong URL: %s</p>\n", url);
		OUT1(" </body>\n");
		OUT1("</html>\n");

		reply->result_code = 200;
		reply->no_cache = 1;
		xfree(reply->content_type);
		reply->content_type = xstrdup("text/html");
		reply->passthru = pass_str;

		html_vars(reply, &msg);
		rc = proxy_reply(conn);
		xfree(msg);
		return 0;
	}

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	if (0 == stat(chat, &st) && S_ISREG(st.st_mode)) {

		fd = open(chat, O_RDONLY|O_BINARY);

		html = xcalloc(st.st_size + 1, sizeof(char));

		if (st.st_size != read(fd, html, st.st_size)) {
			xfree(html);
			html = xstrdup(
				DOCTYPE \
				"<html>\n" \
				" <head>\n" \
				"  <title>%s Chat</title>\n" \
				CONTENT_TYPE \
				STYLESHEET \
				" </head>\n" \
				" <body>\n" \
				"  <div class=\"title\">" PROGNAME " Gateway</div>\n" \
        		"  <p>There was a problem reading the chat HTML files in\n" \
				"  your " PROGNAME " directory.</p>\n" \
				" </body>\n" \
				"</html>\n");
		}

	} else {
		xfree(html);
		reply->user = html = xstrdup(
			DOCTYPE \
			"<html>\n" \
			" <head>\n" \
			"  <title>%s Chat</title>\n" \
			CONTENT_TYPE \
			STYLESHEET \
			" </head>\n" \
			" <body>\n" \
			"  <div class=\"title\">" PROGNAME " Gateway</div>\n" \
        	"  <p>There was no chat HTML file found\n" \
			"  in your " PROGNAME " directory.</p>\n" \
			" </body>\n" \
			"</html>\n");
		reply->content_length = strlen(reply->user);
	}

	if (-1 != fd) {
		close(fd);
		fd = -1;
	}

	html_vars(reply, &html);
	rc = proxy_reply(conn);
	
	xfree(html);
	xfree(chat);

	return rc;
}

/*
 *	proxy_node()
 *	Node Status menu and dispatcher.
 */
static int proxy_node(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	int rc = 0;
	FUN("proxy_node");

	if (0 == strcmp(url, "favicon.ico")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/favicon.ico");
	} else if (0 == strcmp(url, "robots.txt")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/robots.txt");
	} else if (0 == strcmp(url, "node/s15.gif")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/s15.gif");
	} else if (0 == strcmp(url, "node/s30.gif")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/s30.gif");
	} else if (0 == strcmp(url, "node/s45.gif")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/s45.gif");
	} else if (0 == strcmp(url, "node/s60.gif")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/s60.gif");
	} else if (0 == strcmp(url, "node/background.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/background.png");
	} else if (0 == strcmp(url, "node/logo_small.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/logo_small.png");
	} else if (0 == strcmp(url, "node/hedgehog_de.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/hedgehog_de.png");
	} else if (0 == strcmp(url, "node/hedgehog_en.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/hedgehog_en.png");
	} else if (0 == strcmp(url, "node/prev.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/prev.png");
	} else if (0 == strcmp(url, "node/next.png")) {
		rc = proxy_image_ext(conn, g_conf->configpath,
			"node/next.png");
	} else if (0 == strcmp(url, "node/latestbuild.png")) {
		rc = proxy_latestbuild_png(conn);
	} else if (0 == strncmp(url, "node/vt_news_post.gif", 21)) {
		rc = proxy_vt_news_post_gif(conn);
	} else if (0 == strncmp(url, "node/vt_file_up.gif", 19)) {
		rc = proxy_vt_file_up_gif(conn);
	} else if (0 == strncmp(url, "node/loadgraphics", 17)) {
		rc = proxy_loadgraphics_png(conn, url);
	} else if (0 == strncmp(url, "node/fingerprint", 16)) {
		rc = proxy_node_fingerprint_png(conn, url);
	} else if (0 == strncmp(url, "node/bandwidth", 14)) {
		rc = proxy_node_bandwidth_png(conn, url);
	} else if (0 == strcmp(url, "node/keypair.html")) {
		rc = proxy_node_keypair(conn, url);
	} else if (0 == strcmp(url, "node/config.html")) {
		rc = proxy_node_config(conn, url);
	} else if (0 == strcmp(url, "node/process.html")) {
		rc = proxy_node_process(conn, url);
	} else if (0 == strcmp(url, "node/peers.html")) {
		rc = proxy_node_peers(conn, url);
	} else if (0 == strcmp(url, "node/store.html")) {
		rc = proxy_node_store(conn, url);
	} else {
		dst = msg = xcalloc(max, sizeof(char));

		OUT1(DOCTYPE);
		OUT1("<html>\n");
		OUT1(" <head>\n");
		OUT4("  <title>%s %s (%s)</title>\n",
			VERSION,
			LO("status"),
			url);
		OUT1(CONTENT_TYPE);
		OUT1(STYLESHEET);
		OUT1(" </head>\n");
		OUT1(" <body>\n");
    	OUT2("  %s\n", nav(reply, "subtitle", LO("Status")));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/node/keypair.html\" target=\"_top\">%s</a> %s</div>\n",
			LO("Create a"), LO("Keypair"), LO("and show it"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/chat\" target=\"_top\">%s</a> %s</div>\n",
			LO("Show the"), LO("Chat"), LO("page"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/news\" target=\"_top\">%s</a> %s</div>\n",
			LO("Show the"), LO("News"), LO("page"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/node/config.html\" target=\"_top\">%s</a> %s</div>\n",
			LO("Display node"), LO("Config"), LO("for this node"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/node/process.html\" target=\"_top\">%s</a> %s</div>\n",
			LO("Display node"), LO("Process"), LO("information"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/node/peers.html\" target=\"_top\">%s</a> %s</div>\n",
			LO("Show the"), LO("Peer"), LO("status page"));
    	OUT4("  <div class=\"subtitle\">%s <a href=\"/node/store.html\" target=\"_top\">%s</a> %s</div>\n",
			LO("Show the"), LO("Store"), LO("status page"));
		OUT1(" </body>\n");
		OUT1("</html>\n");

		reply->result_code = 200;
		reply->no_cache = 1;
		xfree(reply->content_type);
		reply->content_type = xstrdup("text/html");
		reply->passthru = pass_str;

		html_vars(reply, &msg);
		rc = proxy_reply(conn);
		xfree(msg);
	}
	
	return rc;
}

static char *spc(size_t size)
{
#undef	BUFSIZE
#undef	BUFNUM
#define	BUFSIZE	128
#define	BUFNUM	4
	static char buff[BUFNUM][BUFSIZE];
	static int which = 0;
	char *dst;

	if (size + 1 >= BUFSIZE) {
		size = BUFSIZE - 1;
	}
	which = (which + 1) % BUFNUM;
	dst = buff[which];
	memset(dst, ' ', size);
	dst[size] = '\0';

	return buff[which];
}

static const char *strpad(const char *src, size_t size)
{
#undef	BUFSIZE
#undef	BUFNUM
#define	BUFSIZE	128
#define	BUFNUM	4
	static char buff[BUFNUM][BUFSIZE];
	static int which = 0;
	size_t len;
	char *dst;

	len = strlen(src);
	if (len > size)
		size = len;
	if (size + 1 >= BUFSIZE) {
		size = BUFSIZE - 1;
	}
	which = (which + 1) % BUFNUM;
	dst = buff[which];
	len = pm_snprintf(dst, size + 1, "%s", src);
	if (len < size) {
		dst += len;
		pm_spprintf(dst, buff[which], size, "%s",
			spc(size - len));
	}
	return buff[which];
}

static char *decpad(size_t num, size_t size)
{
#undef	BUFSIZE
#undef	BUFNUM
#define	BUFSIZE	128
#define	BUFNUM	4
	static char buff[BUFNUM][BUFSIZE];
	static int which = 0;
	char *dst;
	size_t len;

	if (size + 1 >= BUFSIZE) {
		size = BUFSIZE - 1;
	}
	which = (which + 1) % BUFNUM;
	dst = buff[which];
	len = pm_snprintf(dst, size + 1, "%d", num);
	if (len < size) {
		pm_snprintf(dst, size + 1, "%s%d",
			spc(size - len), num);
	}
	return buff[which];
}

static char *strchr_nonurl(const char *src)
{
	static char nonurl[] = "\t\r\n \"'<>([{}])";
	char *shortest = NULL;
	size_t i;
	for (i = 0; i < sizeof(nonurl); i++) {
		char *end_of_url = strchr(src, nonurl[i]);
		if (NULL == end_of_url)
			continue;
		if (NULL == shortest || end_of_url < shortest)
			shortest = end_of_url;
	}
	return shortest;
}

static char *proxy_news_link(conn_t *conn,
	const char *linktype, const char *prefix,
	const char **psrc, const char *eot)
{
#undef	BUFNUM
#define	BUFNUM	8
	proxy_reply_t *reply = conn->temp;
	static char *buff[BUFNUM] = {
		NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL
	};
	static int which = 0;
	const char *src = *psrc;
	size_t len = (size_t)(eot - src);
	char *filename = xcalloc(len + 1, sizeof(char));
	char *encoded = NULL;

	which = (which + 1) % BUFNUM;
	xfree(buff[which]);

	strncpy(filename, src, len);
	url_encode(&encoded, filename, 1);
	if (NULL == linktype) {
		pm_asprintf(&buff[which], 
			"<a href=\"%s%s\">%s</a>",
			prefix, encoded, filename);
	} else {
		pm_asprintf(&buff[which],
			"<span class=\"hilite\">%s:</span>&nbsp;<a href=\"%s%s\">%s</a>",
			LO(linktype), prefix, encoded, filename);
	}
	xfree(encoded);
	xfree(filename);
	*psrc = eot - 1;

	return buff[which];
}

#define	XLT_ENTITY(ent,xlt)	\
	if (0 == strncmp(src, ent, sizeof(ent) - 1)) { \
		OUT2("%s", xlt); \
		src += sizeof(ent) - 1 - 1; \
		continue; \
	}

#define	PASS_TAG(tag)	\
	if (0 == strncmp(src, tag, sizeof(tag) - 1)) { \
		OUT2("%s", tag); \
		src += sizeof(tag) - 1 - 1; \
		continue; \
	}

static char *proxy_news_body(conn_t *conn, const char *body)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL, *mid, *eot, *ast, ch1, ch2;
	const char *src;
	FUN("proxy_news_body");

	dst = msg = xcalloc(max, sizeof(char));

	for (src = body; *src; src++) {
		if (*src == '\n') {
			/* for every carriage return, output a <br />, too */
			OUT1("<br />\n");
			continue;
		}
		if (0 == strncmp(src, FROST_MSG, FROST_MSG_LEN)) {
			OUT2("<span class=\"hilite\">%s</span>\n",
				LO("Frost signed message"));
				src += FROST_MSG_LEN - 1;
			continue;
		}
		if (0 == strncmp(src, FROST_SIG, FROST_SIG_LEN)) {
			if (NULL != (eot = strstr(src, FROST_EOS))) {
				OUT2("<span class=\"hilite\">%s</span>\n",
					LO("Frost message signature"));
				src = eot + FROST_EOS_LEN - 1;
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, FROST_KEY, FROST_KEY_LEN)) {
			if (NULL != (eot = strstr(src, FROST_EOK))) {
				OUT2("<span class=\"hilite\">%s</span>\n",
					LO("Frost key"));
				src = eot + FROST_EOK_LEN - 1;
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, REPLY_HDR, REPLY_HDR_LEN)) {
			if (NULL != (mid = strstr(src + 1, REPLY_MID)) &&
				NULL != (eot = strstr(mid + 1, REPLY_END))) {
				ch1 = *mid;
				*mid = '\0';
				ch2 = *eot;
				*eot = '\0';
				OUT4("<div class=\"hilite\"><b>%s</b> %s %s:</div>",
					src + REPLY_HDR_LEN,
					LO("wrote on"),
					mid + REPLY_MID_LEN);
				*mid = ch1; 
				*eot = ch2;
				src = &eot[REPLY_END_LEN-1];
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "ftp://", 6)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 6;
				OUT2("%s", proxy_news_link(conn, "FTP",
					"/__CHECKED_FTP__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "gopher://", 9)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 9;
				OUT2("%s", proxy_news_link(conn, "GOPHER",
					"/__CHECKED_GOPHER__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "http://127.0.0.1:9999/", 22) ||
			0 == strncmp(src, "http://localhost:9999/", 22)) {
			/* cut out proxy references */
			src += 22 - 1;
			continue;
		}
		if (0 == strncmp(src, "http://", 7)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 7;
				OUT2("%s", proxy_news_link(conn, "HTTP",
					"/__CHECKED_HTTP__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "https://", 8)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 8;
				OUT2("%s", proxy_news_link(conn, "HTTPS",
					"/__CHECKED_HTTPS__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "telnet://", 9)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 9;
				OUT2("%s", proxy_news_link(conn, "TELNET",
					"/__CHECKED_TELNET__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "rtsp://", 7)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 8;
				OUT2("%s", proxy_news_link(conn, "RTSP",
					"/__CHECKED_RTSP__", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "/CHK@", 5)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 1;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "entropy:CHK@", 12)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 8;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "CHK@", 4)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "/SSK@", 5)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 1;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "entropy:SSK@", 12)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 8;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "SSK@", 4)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "/KSK@", 5)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 1;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "entropy:KSK@", 12)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				src += 8;
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
		if (0 == strncmp(src, "KSK@", 4)) {
			if (NULL != (eot = strchr_nonurl(src))) {
				OUT2("%s", proxy_news_link(conn, NULL, "/", &src, eot));
			} else {
				OUT2("%c", *src);
			}
			continue;
		}
/* supported URL encoded strings */
		if (*src == '&') {
			XLT_ENTITY("&quot;","&#34;")
			XLT_ENTITY("&amp;","&#38;")
			XLT_ENTITY("&lt;","&#60;")
			XLT_ENTITY("&gt;","&#62;")
			XLT_ENTITY("&nbsp;","&#160;")
			XLT_ENTITY("&iexcl;","&#161;")
			XLT_ENTITY("&cent;","&#162;")
			XLT_ENTITY("&pound;","&#163;")
			XLT_ENTITY("&curren;","&#164;")
			XLT_ENTITY("&yen;","&#165;")
			XLT_ENTITY("&brvbar;","&#166;")
			XLT_ENTITY("&sect;","&#167;")
			XLT_ENTITY("&uml;","&#168;")
			XLT_ENTITY("&copy;","&#169;")
			XLT_ENTITY("&ordf;","&#170;")
			XLT_ENTITY("&laquo;","&#171;")
			XLT_ENTITY("&not;","&#172;")
			XLT_ENTITY("&shy;","&#173;")
			XLT_ENTITY("&reg;","&#174;")
			XLT_ENTITY("&macr;","&#175;")
			XLT_ENTITY("&deg;","&#176;")
			XLT_ENTITY("&plusmn;","&#177;")
			XLT_ENTITY("&sup2;","&#178;")
			XLT_ENTITY("&sup3;","&#179;")
			XLT_ENTITY("&acute;","&#180;")
			XLT_ENTITY("&micro;","&#181;")
			XLT_ENTITY("&para;","&#182;")
			XLT_ENTITY("&middot;","&#183;")
			XLT_ENTITY("&cedil;","&#184;")
			XLT_ENTITY("&sup1;","&#185;")
			XLT_ENTITY("&ordm;","&#186;")
			XLT_ENTITY("&raquo;","&#187;")
			XLT_ENTITY("&frac14;","&#188;")
			XLT_ENTITY("&frac12;","&#189;")
			XLT_ENTITY("&frac34;","&#190;")
			XLT_ENTITY("&iquest;","&#191;")
			XLT_ENTITY("&Agrave;","&#192;")
			XLT_ENTITY("&Aacute;","&#193;")
			XLT_ENTITY("&Acirc;","&#194;")
			XLT_ENTITY("&Atilde;","&#195;")
			XLT_ENTITY("&Auml;","&#196;")
			XLT_ENTITY("&Aring;","&#197;")
			XLT_ENTITY("&AElig;","&#198;")
			XLT_ENTITY("&Ccedil;","&#199;")
			XLT_ENTITY("&Egrave;","&#200;")
			XLT_ENTITY("&Eacute;","&#201;")
			XLT_ENTITY("&Ecirc;","&#202;")
			XLT_ENTITY("&Euml;","&#203;")
			XLT_ENTITY("&Igrave;","&#204;")
			XLT_ENTITY("&Iacute;","&#205;")
			XLT_ENTITY("&Icirc;","&#206;")
			XLT_ENTITY("&Iuml;","&#207;")
			XLT_ENTITY("&ETH;","&#208;")
			XLT_ENTITY("&Ntilde;","&#209;")
			XLT_ENTITY("&Ograve;","&#210;")
			XLT_ENTITY("&Oacute;","&#211;")
			XLT_ENTITY("&Ocirc;","&#212;")
			XLT_ENTITY("&Otilde;","&#213;")
			XLT_ENTITY("&Ouml;","&#214;")
			XLT_ENTITY("&times;","&#215;")
			XLT_ENTITY("&Oslash;","&#216;")
			XLT_ENTITY("&Ugrave;","&#217;")
			XLT_ENTITY("&Uacute;","&#218;")
			XLT_ENTITY("&Ucirc;","&#219;")
			XLT_ENTITY("&Uuml;","&#220;")
			XLT_ENTITY("&Yacute;","&#221;")
			XLT_ENTITY("&THORN;","&#222;")
			XLT_ENTITY("&szlig;","&#223;")
			XLT_ENTITY("&agrave;","&#224;")
			XLT_ENTITY("&aacute;","&#225;")
			XLT_ENTITY("&acirc;","&#226;")
			XLT_ENTITY("&atilde;","&#227;")
			XLT_ENTITY("&auml;","&#228;")
			XLT_ENTITY("&aring;","&#229;")
			XLT_ENTITY("&aelig;","&#230;")
			XLT_ENTITY("&ccedil;","&#231;")
			XLT_ENTITY("&egrave;","&#232;")
			XLT_ENTITY("&eacute;","&#233;")
			XLT_ENTITY("&ecirc;","&#234;")
			XLT_ENTITY("&euml;","&#235;")
			XLT_ENTITY("&igrave;","&#236;")
			XLT_ENTITY("&iacute;","&#237;")
			XLT_ENTITY("&icirc;","&#238;")
			XLT_ENTITY("&iuml;","&#239;")
			XLT_ENTITY("&eth;","&#240;")
			XLT_ENTITY("&ntilde;","&#241;")
			XLT_ENTITY("&ograve;","&#242;")
			XLT_ENTITY("&oacute;","&#243;")
			XLT_ENTITY("&ocirc;","&#244;")
			XLT_ENTITY("&otilde;","&#245;")
			XLT_ENTITY("&ouml;","&#246;")
			XLT_ENTITY("&divide;","&#247;")
			XLT_ENTITY("&oslash;","&#248;")
			XLT_ENTITY("&ugrave;","&#249;")
			XLT_ENTITY("&uacute;","&#250;")
			XLT_ENTITY("&ucirc;","&#251;")
			XLT_ENTITY("&uuml;","&#252;")
			XLT_ENTITY("&yacute;","&#253;")
			XLT_ENTITY("&thorn;","&#254;")
			XLT_ENTITY("&yuml;","&#255;")
			XLT_ENTITY("&OElig;","&#338;")
			XLT_ENTITY("&Yuml;","&#376;")
			XLT_ENTITY("&fnof;","&#402;")
			XLT_ENTITY("&circ;","&#710;")
			XLT_ENTITY("&tilde;","&#732;")
			XLT_ENTITY("&Alpha;","&#913;")
			XLT_ENTITY("&Beta;","&#914;")
			XLT_ENTITY("&Gamma;","&#915;")
			XLT_ENTITY("&Delta;","&#916;")
			XLT_ENTITY("&Epsilon;","&#917;")
			XLT_ENTITY("&Zeta;","&#918;")
			XLT_ENTITY("&Eta;","&#919;")
			XLT_ENTITY("&Theta;","&#920;")
			XLT_ENTITY("&Iota;","&#921;")
			XLT_ENTITY("&Kappa;","&#922;")
			XLT_ENTITY("&Lambda;","&#923;")
			XLT_ENTITY("&Mu;","&#924;")
			XLT_ENTITY("&Nu;","&#925;")
			XLT_ENTITY("&Xi;","&#926;")
			XLT_ENTITY("&Omicron;","&#927;")
			XLT_ENTITY("&Pi;","&#928;")
			XLT_ENTITY("&Rho;","&#929;")
			XLT_ENTITY("&Sigma;","&#931;")
			XLT_ENTITY("&Tau;","&#932;")
			XLT_ENTITY("&Upsilon;","&#933;")
			XLT_ENTITY("&Phi;","&#934;")
			XLT_ENTITY("&Chi;","&#935;")
			XLT_ENTITY("&Psi;","&#936;")
			XLT_ENTITY("&Omega;","&#937;")
			XLT_ENTITY("&alpha;","&#x03B1;")
			XLT_ENTITY("&beta;","&#946;")
			XLT_ENTITY("&gamma;","&#947;")
			XLT_ENTITY("&delta;","&#948;")
			XLT_ENTITY("&epsilon;","&#949;")
			XLT_ENTITY("&zeta;","&#950;")
			XLT_ENTITY("&eta;","&#951;")
			XLT_ENTITY("&theta;","&#952;")
			XLT_ENTITY("&iota;","&#953;")
			XLT_ENTITY("&kappa;","&#954;")
			XLT_ENTITY("&lambda;","&#955;")
			XLT_ENTITY("&mu;","&#956;")
			XLT_ENTITY("&nu;","&#957;")
			XLT_ENTITY("&xi;","&#958;")
			XLT_ENTITY("&omicron;","&#959;")
			XLT_ENTITY("&pi;","&#960;")
			XLT_ENTITY("&rho;","&#961;")
			XLT_ENTITY("&sigmaf;","&#962;")
			XLT_ENTITY("&sigma;","&#963;")
			XLT_ENTITY("&tau;","&#964;")
			XLT_ENTITY("&upsilon;","&#965;")
			XLT_ENTITY("&phi;","&#966;")
			XLT_ENTITY("&chi;","&#967;")
			XLT_ENTITY("&psi;","&#968;")
			XLT_ENTITY("&omega;","&#969;")
			XLT_ENTITY("&thetasym;","&#977;")
			XLT_ENTITY("&upsih;","&#978;")
			XLT_ENTITY("&piv;","&#982;")
			XLT_ENTITY("&bull;","&#8226;")
			XLT_ENTITY("&hellip;","&#8230;")
			XLT_ENTITY("&prime;","&#8242;")
			XLT_ENTITY("&Prime;","&#8243;")
			XLT_ENTITY("&oline;","&#8254;")
			XLT_ENTITY("&frasl;","&#8260;")
			XLT_ENTITY("&weierp;","&#8472;")
			XLT_ENTITY("&image;","&#8465;")
			XLT_ENTITY("&real;","&#8476;")
			XLT_ENTITY("&trade;","&#8482;")
			XLT_ENTITY("&alefsym;","&#8501;")
			XLT_ENTITY("&larr;","&#8592;")
			XLT_ENTITY("&uarr;","&#8593;")
			XLT_ENTITY("&rarr;","&#8594;")
			XLT_ENTITY("&darr;","&#8595;")
			XLT_ENTITY("&harr;","&#8596;")
			XLT_ENTITY("&crarr;","&#8629;")
			XLT_ENTITY("&lArr;","&#8656;")
			XLT_ENTITY("&uArr;","&#8657;")
			XLT_ENTITY("&rArr;","&#8658;")
			XLT_ENTITY("&dArr;","&#8659;")
			XLT_ENTITY("&hArr;","&#8660;")
			XLT_ENTITY("&forall;","&#8704;")
			XLT_ENTITY("&part;","&#8706;")
			XLT_ENTITY("&exist;","&#8707;")
			XLT_ENTITY("&empty;","&#8709;")
			XLT_ENTITY("&nabla;","&#8711;")
			XLT_ENTITY("&isin;","&#8712;")
			XLT_ENTITY("&notin;","&#8713;")
			XLT_ENTITY("&ni;","&#8715;")
			XLT_ENTITY("&prod;","&#8719;")
			XLT_ENTITY("&sum;","&#8721;")
			XLT_ENTITY("&minus;","&#8722;")
			XLT_ENTITY("&lowast;","&#8727;")
			XLT_ENTITY("&radic;","&#8730;")
			XLT_ENTITY("&prop;","&#8733;")
			XLT_ENTITY("&infin;","&#8734;")
			XLT_ENTITY("&ang;","&#8736;")
			XLT_ENTITY("&and;","&#8743;")
			XLT_ENTITY("&or;","&#8744;")
			XLT_ENTITY("&cap;","&#8745;")
			XLT_ENTITY("&cup;","&#8746;")
			XLT_ENTITY("&int;","&#8747;")
			XLT_ENTITY("&there4;","&#8756;")
			XLT_ENTITY("&sim;","&#8764;")
			XLT_ENTITY("&cong;","&#8773;")
			XLT_ENTITY("&asymp;","&#8776;")
			XLT_ENTITY("&ne;","&#8800;")
			XLT_ENTITY("&equiv;","&#8801;")
			XLT_ENTITY("&le;","&#8804;")
			XLT_ENTITY("&ge;","&#8805;")
			XLT_ENTITY("&sub;","&#8834;")
			XLT_ENTITY("&sup;","&#8835;")
			XLT_ENTITY("&nsub;","&#8836;")
			XLT_ENTITY("&sube;","&#8838;")
			XLT_ENTITY("&supe;","&#8839;")
			XLT_ENTITY("&oplus;","&#8853;")
			XLT_ENTITY("&otimes;","&#8855;")
			XLT_ENTITY("&perp;","&#8869;")
			XLT_ENTITY("&sdot;","&#8901;")
			XLT_ENTITY("&lceil;","&#8968;")
			XLT_ENTITY("&rceil;","&#8969;")
			XLT_ENTITY("&lfloor;","&#8970;")
			XLT_ENTITY("&rfloor;","&#8971;")
			XLT_ENTITY("&lang;","&#9001;")
			XLT_ENTITY("&rang;","&#9002;")
			XLT_ENTITY("&loz;","&#9674;")
			XLT_ENTITY("&spades;","&#9824;")
			XLT_ENTITY("&clubs;","&#9827;")
			XLT_ENTITY("&hearts;","&#9829;")
			XLT_ENTITY("&diams;","&#9830;")
			XLT_ENTITY("&oelig;","&#339;")
			XLT_ENTITY("&Scaron;","&#352;")
			XLT_ENTITY("&scaron;","&#353;")
			XLT_ENTITY("&ensp;","&#8194;")
			XLT_ENTITY("&emsp;","&#8195;")
			XLT_ENTITY("&thinsp;","&#8201;")
			XLT_ENTITY("&zwnj;","&#8204;")
			XLT_ENTITY("&zwj;","&#8205;")
			XLT_ENTITY("&lrm;","&#8206;")
			XLT_ENTITY("&rlm;","&#8207;")
			XLT_ENTITY("&ndash;","&#8211;")
			XLT_ENTITY("&mdash;","&#8212;")
			XLT_ENTITY("&lsquo;","&#8216;")
			XLT_ENTITY("&rsquo;","&#8217;")
			XLT_ENTITY("&sbquo;","&#8218;")
			XLT_ENTITY("&ldquo;","&#8220;")
			XLT_ENTITY("&rdquo;","&#8221;")
			XLT_ENTITY("&bdquo;","&#8222;")
			XLT_ENTITY("&dagger;","&#8224;")
			XLT_ENTITY("&Dagger;","&#8225;")
			XLT_ENTITY("&permil;","&#8240;")
			XLT_ENTITY("&lsaquo;","&#8249;")
			XLT_ENTITY("&rsaquo;","&#8250;")
			XLT_ENTITY("&euro;","&#8364;")
			/* html special character ampersand */
			OUT1("&amp;");
			continue;
		}
/* supported HTML tags */
		if (*src == '<') {
			/* cut out comments */
			if (src[1] == '!') {
				if (NULL != (eot = strchr(src, '>'))) {
					src = eot;
				} else {
					OUT2("%c", *src);
				}
				continue;
			}
			PASS_TAG("<abbr>")
			PASS_TAG("</abbr>")
			PASS_TAG("<acronym>")
			PASS_TAG("</acronym>")
			PASS_TAG("<address>")
			PASS_TAG("</address>")
			PASS_TAG("<b>")
			PASS_TAG("</b>")
			PASS_TAG("<big>")
			PASS_TAG("</big>")
			PASS_TAG("<blockquote>")
			PASS_TAG("</blockquote>")
			PASS_TAG("<br>")
			PASS_TAG("<br />")
			PASS_TAG("<center>")
			PASS_TAG("</center>")
			PASS_TAG("<cite>")
			PASS_TAG("</cite>")
			PASS_TAG("<code>")
			PASS_TAG("</code>")
			PASS_TAG("<dd>")
			PASS_TAG("</dd>")
			PASS_TAG("<del>")
			PASS_TAG("</del>")
			PASS_TAG("<dfn>")
			PASS_TAG("</dfn>")
			PASS_TAG("<dir>")
			PASS_TAG("</dir>")
			PASS_TAG("<dl>")
			PASS_TAG("</dl>")
			PASS_TAG("<dt>")
			PASS_TAG("</dt>")
			PASS_TAG("<em>")
			PASS_TAG("</em>")
			PASS_TAG("<h1>")
			PASS_TAG("</h1>")
			PASS_TAG("<h2>")
			PASS_TAG("</h2>")
			PASS_TAG("<h3>")
			PASS_TAG("</h3>")
			PASS_TAG("<h4>")
			PASS_TAG("</h4>")
			PASS_TAG("<h5>")
			PASS_TAG("</h5>")
			PASS_TAG("<h6>")
			PASS_TAG("</h6>")
			PASS_TAG("<hr>")
			PASS_TAG("<hr />")
			PASS_TAG("<ins>")
			PASS_TAG("</ins>")
			PASS_TAG("<i>")
			PASS_TAG("</i>")
			PASS_TAG("<kbd>")
			PASS_TAG("</kbd>")
			PASS_TAG("<li>")
			PASS_TAG("</li>")
			PASS_TAG("<menu>")
			PASS_TAG("</menu>")
			PASS_TAG("<ol>")
			PASS_TAG("</ol>")
			PASS_TAG("<p>")
			PASS_TAG("</p>")
			PASS_TAG("<pre>")
			PASS_TAG("</pre>")
			PASS_TAG("<s>")
			PASS_TAG("</s>")
			PASS_TAG("<samp>")
			PASS_TAG("</samp>")
			PASS_TAG("<small>")
			PASS_TAG("</small>")
			PASS_TAG("<strike>")
			PASS_TAG("</strike>")
			PASS_TAG("<strong>")
			PASS_TAG("</strong>")
			PASS_TAG("<sub>")
			PASS_TAG("</sub>")
			PASS_TAG("<sup>")
			PASS_TAG("</sup>")
			PASS_TAG("<tt>")
			PASS_TAG("</tt>")
			PASS_TAG("<u>")
			PASS_TAG("</u>")
			PASS_TAG("<ul>")
			PASS_TAG("</ul>")
			PASS_TAG("<var>")
			PASS_TAG("</var>")
			if (0 == strncmp(src, ATTACHED1_BEG, ATTACHED1_BEG_LEN)) {
				if (NULL != (eot = strstr(src, ATTACHED1_END)) &&
					NULL != (ast = strstr(src, ATTACHED1_MID)) && ast < eot) {
					chkey_t key;
					char *filename, *basename;
					char *encoded;
					/*
			 	 	* found an inline attachment
			 	 	* the format is like this:
			 	 	* <attached>filename.ext * CHK@something</attached>
			 	 	*/
					ch1 = *ast;
					*ast = '\0';
					pm_asprintf(&filename, "%s", src + ATTACHED1_BEG_LEN);
					basename = strrchr(filename, '\\');
					if (NULL == basename)
						basename = strrchr(filename, '/');
					if (NULL == basename)
						basename = filename;
					else
						basename++;
					/* send the key through is_valid_chk() */
					is_valid_chk(&key, ast + ATTACHED1_MID_LEN);
					url_encode(&encoded, filename, 1);
					OUT5("<span class=\"hilite\">%s:</span>&nbsp;" \
						"<a href=\"/%s/%s\">%s</a>",
						LO("Attachment"), key_long(&key), encoded, basename);
					*ast = ch1;
					/* skip over the trailing </attached> */
					src = eot + ATTACHED1_END_LEN - 1;
					xfree(encoded);
					xfree(filename);
				} else {
					OUT2("%c", *src);
				}
				continue;
			}
			if (0 == strncmp(src, ATTACHED2_BEG, ATTACHED2_BEG_LEN)) {
				if (NULL != (eot = strstr(src, ATTACHED2_END)) &&
					NULL != (ast = strstr(src, ATTACHED2_MID)) && ast < eot) {
					chkey_t key;
					char *filename, *basename;
					char *encoded;
					/*
			 	 	* found an inline attachment
			 	 	* the format is like this:
			 	 	* <!attached>filename.ext * CHK@something<!/attached>
			 	 	*/
					ch1 = *ast;
					*ast = '\0';
					pm_asprintf(&filename, "%s", src + ATTACHED2_BEG_LEN);
					basename = strrchr(filename, '\\');
					if (NULL == basename)
						basename = strrchr(filename, '/');
					if (NULL == basename)
						basename = filename;
					else
						basename++;
					/* send the key through is_valid_chk() */
					is_valid_chk(&key, ast + ATTACHED2_MID_LEN);
					url_encode(&encoded, filename, 1);
					OUT5("<span class=\"hilite\">%s:</span>&nbsp;" \
						"<a href=\"/%s/%s\">%s</a>",
						LO("Attachment"), key_long(&key), encoded, basename);
					*ast = ch1;
					/* skip over the trailing <!/attached> */
					src = eot + ATTACHED2_END_LEN - 1;
					xfree(encoded);
					xfree(filename);
				} else {
					OUT2("%c", *src);
				}
				continue;
			}
			/* convert unsupported HTML tags */
			/* emit html special character less than */
			OUT1("&lt;");
			continue;
		}
		if (*src == '>') {
			/* html special character greater than */
			OUT1("&gt;");
			continue;
		}
		OUT2("%c", *src);
	}

	return msg;
}

int sort_boards_by_name(const void *p1, const void *p2)
{
	const news_board_t *b1 = *(const news_board_t **)p1;
	const news_board_t *b2 = *(const news_board_t **)p2;
	int rc;
	if (0 != (rc = strcmp(b2->board, b1->board)))
		return rc;
	if (0 != (rc = b1->date.tm_year - b2->date.tm_year))
		return rc;
	if (0 != (rc = b1->date.tm_mon - b2->date.tm_mon))
		return rc;
	rc = b1->date.tm_mday - b2->date.tm_mday;
	return rc;
}

#undef	MIN
#define	MIN(a,b) ((a)<(b)?(a):(b))
/*
 *	proxy_news()
 *	Access to the data collected by the news() thread
 */
static int proxy_news(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	size_t cols[9] = {3,0,16,16,3,3,3,3,10};
	char *dst, *msg = NULL;
	char *boards = NULL, *board;
	char *exp_board = NULL;
	const char *class;
	size_t i, j, k, maxlen, messages, yesterday, today, unseen, board_cnt;
	time_t t0, t1, t_now, t_min, t_max;
	struct tm tm0, tm1, exp_tm;
	int exp_new = 0, rc = 0;
	FUN("proxy_news");

	url += MIN(strlen(url), strlen("news/"));

	if (0 != osd_sem_wait(&g_news->sem)) {
		return -1;
	}

	t_now = t0 = time(NULL);
	memcpy(&tm0, gmtime(&t0), sizeof(tm0));
	tm0.tm_hour = 0;
	tm0.tm_min = 0;
	tm0.tm_sec = 0;
	t0 = timegm(&tm0);
	tm1 = tm0;
	tm1.tm_mday -= 1;
	t1 = timegm(&tm1);
	memcpy(&tm1, gmtime(&t1), sizeof(tm1));

	memset(&exp_tm, 0, sizeof(exp_tm));

	if (strlen(url) > 0) {
		char *slash;
		exp_board = xstrdup(url);
		/* see if a slash follows the board name */
		if (NULL != (slash = strchr(exp_board, '/'))) {
			*slash++ = '\0';
			/* want to filter new messages? */
			if (0 == strcasecmp(slash, "new")) {
				exp_new = 1;
			} else {
				parse_tm_date(&exp_tm, slash);
			}
		}
	}

	/* make a copy of the configured news_boards string and split it up */
	k = strlen(g_conf->news_boards);
	boards = xcalloc(k + 2, sizeof(char));
	strncpy(boards, g_conf->news_boards, k + 1);
	for (i = 0, j = 0, maxlen = 0; i < k; i++, j++) {
		if (',' == boards[i]) {
			boards[i] = '\0';
			if (j > maxlen) {
				maxlen = j;
			}
			j = 0;
		}
	}
	if (j > maxlen) {
		maxlen = j;
	}
	cols[1] = maxlen;

	xfree(reply->charset);
	reply->charset = xstrdup("utf-8");

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("News"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", "%s (%s)", LO("News"), url));

	OUT9("  <div class=\"subtitle\">%s '%s' %s %d %s %04d-%02d-%02d</div>\n",
		LO("Currently scanning board"),
		g_news->board,
		LO("entry"),
		(int)g_news->slot,
		LO("for date"),
		g_news->date.tm_year + 1900,
		g_news->date.tm_mon + 1,
		g_news->date.tm_mday);

	OUT1("  <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");
	OUT1("   <colgroup>\n");
	OUT1("    <col width=\"5%%\">\n");
	OUT1("    <col width=\"15%%\">\n");
	OUT1("    <col width=\"15%%\">\n");
	OUT1("    <col width=\"15%%\">\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("   </colgroup>\n");

	OUT1("   <tr>\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("No."), cols[0]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("board"), cols[1]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("oldest"), cols[2]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("newest"), cols[3]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("total"), cols[4]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("yesterday"), cols[5]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("today"), cols[6]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("new"), cols[7]));
	OUT2("    <th class=\"label\">%s</th>\n",
		strpad(LO("create"), cols[8]));
	OUT1("   </tr>\n");

	/* Specific boards selectors */
	for (board = boards, j = 0; *board; board += strlen(board) + 1) {
		if (NULL != exp_board && 0 == strcasecmp(exp_board, board)) {
			class = "hilite";
		} else {
			class = "content";
		}
		/* board */
		OUT1("   <tr>\n");
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, decpad(++j, cols[0]));
		OUT4("    <td class=\"%s\" nowrap><a href=\"/news/%s#m0\">%s</a></td>\n",
			class, board, strpad(board, cols[1]));
		t_min = (time_t)0x7fffffff;
		t_max = (time_t)0x00000000;
		messages = 0;
		yesterday = 0;
		today = 0;
		unseen = 0;
		for (i = 0; i < g_news->entry_cnt; i++) {
			news_entry_t *e = g_news->entrylist[i];
			if (NULL == e)
				continue;
			if (0 != strcasecmp(e->board, board)) {
				continue;
			}
			messages++;
			if (0 == e->seen) {
				unseen++;
			}
			if (e->time < t_min) {
				t_min = e->time;
			}
			if (e->time > t_max) {
				t_max = e->time;
			}
			if (e->time - t0 >= 0) {
				today++;
			} else if (e->time + 24 * 60 * 60 - t0 >= 0) {
				yesterday++;
			}
		}
		if (messages > 0) {
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad(age_str(LANG,CHARSET,t_now,t_min), cols[2]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad(age_str(LANG,CHARSET,t_now,t_max), cols[3]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, decpad(messages, cols[4]));
			if (yesterday > 0) {
				OUT7("    <td class=\"%s\"><a href=\"/news/%s/%04d-%02d-%02d#m0\">%s</a></td>\n",
					class,
					board,
					tm1.tm_year + 1900,
					tm1.tm_mon + 1,
					tm1.tm_mday,
					decpad(yesterday, cols[5]));
			} else {
				OUT3("    <td class=\"%s\">%s</td>\n",
					class, decpad(yesterday, cols[5]));
			}
			if (today > 0) {
				OUT7("    <td class=\"%s\"><a href=\"/news/%s/%04d-%02d-%02d#m0\">%s</a></td>\n",
					class,
					board,
					tm0.tm_year + 1900,
					tm0.tm_mon + 1,
					tm0.tm_mday,
					decpad(today, cols[6]));
			} else {
				OUT3("    <td class=\"%s\">%s</td>\n",
					class, decpad(today, cols[6]));
			}
			if (unseen > 0) {
				OUT4("    <td class=\"%s\"><a href=\"/news/%s/new#m0\">%s</a></td>\n",
					class, board, decpad(unseen, cols[7]));
			} else {
				OUT3("    <td class=\"%s\">%s</td>\n",
					class, decpad(unseen, cols[7]));
			}
		} else {
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[2]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[3]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[4]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[5]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[6]));
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, strpad("-", cols[7]));
		}
		OUT4("    <td class=\"%s\"><a href=\"/news.create?board=%s\">%s</a></td>\n",
			"button", board, strpad(LO("post"), cols[8]));
		OUT1("   </tr>\n");
	}

	/* The all boards selector */
	if (NULL != exp_board && 0 == strcmp("*", exp_board)) {
		class = "hilite";
	} else {
		class = "content";
	}
	OUT1("   <tr>\n");
	OUT3("    <td class=\"%s\">%s</td>\n",
		class, strpad(LO("all"), cols[0]));
	if (0 == strcmp(class, "hilite")) {
		OUT3("    <td class=\"%s\" nowrap>%s</td>\n",
				class, strpad(LO("all boards"), cols[1]));
	} else {
		OUT4("    <td class=\"%s\" nowrap><a href=\"/news/%s\">%s</a></td>\n",
				class, "*", strpad(LO("all boards"), cols[1]));
	}
	t_min = (time_t)0x7fffffff;
	t_max = (time_t)0x00000000;
	messages = 0;
	yesterday = 0;
	today = 0;
	unseen = 0;
	for (i = 0; i < g_news->entry_cnt; i++) {
		news_entry_t *e = g_news->entrylist[i];
		if (NULL == e)
			continue;
		messages++;
		if (0 == e->seen) {
			unseen++;
		}
		if (e->time < t_min) {
			t_min = e->time;
		}
		if (e->time > t_max) {
			t_max = e->time;
		}
		if (e->time - t0 >= 0) {
			today++;
		} else if (e->time + 24 * 60 * 60 - t0 >= 0) {
			yesterday++;
		}
	}
	if (messages > 0) {
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad(age_str(LANG,CHARSET,t_now,t_min), cols[2]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad(age_str(LANG,CHARSET,t_now,t_max), cols[3]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, decpad(messages, cols[4]));
		if (yesterday > 0) {
			OUT6("    <td class=\"%s\"><a href=\"/news/*/%04d-%02d-%02d\">%s</a></td>\n",
				class,
				tm1.tm_year + 1900,
				tm1.tm_mon + 1,
				tm1.tm_mday,
				decpad(yesterday, cols[5]));
		} else {
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, decpad(yesterday, cols[5]));
		}
		if (today > 0) {
			OUT6("    <td class=\"%s\"><a href=\"/news/*/%04d-%02d-%02d\">%s</a></td>\n",
				class,
				tm0.tm_year + 1900,
				tm0.tm_mon + 1,
				tm0.tm_mday,
				decpad(today, cols[6]));
		} else {
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, decpad(today, cols[6]));
		}
		if (unseen > 0) {
			OUT3("    <td class=\"%s\"><a href=\"/news/*/new\">%s</a></td>\n",
				class, decpad(unseen, cols[7]));
		} else {
			OUT3("    <td class=\"%s\">%s</td>\n",
				class, decpad(unseen, cols[7]));
		}
	} else {
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[2]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[3]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[4]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[5]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[6]));
		OUT3("    <td class=\"%s\">%s</td>\n",
			class, strpad("-", cols[7]));
	}
	OUT3("    <td class=\"%s\"><a href=\"/news?rescan=1\">%s</a></td>\n",
		"button", strpad(LO("rescan"), cols[8]));
	OUT1("   </tr>\n");

	OUT1("   <tr class=\"title\">\n");
	OUT2("    <td colspan=\"9\"><b>%s:</b></td>",
		LO("I found the following board announcements, besides the ones you subscribed"));
	OUT1("   </tr>\n");
	OUT1("   <tr>\n");
	OUT1("    <td colspan=\"9\">\n");

	board_cnt = g_news->board_cnt;
	if (board_cnt <= 0) {
		OUT2("%s", LO("none yet"));
	} else {
		news_board_t **announcements = NULL;
		char *last = "";

		announcements = (news_board_t **)
			xcalloc(board_cnt, sizeof(news_board_t *));

		for (i = 0, j = 0; i < board_cnt; i++) {
			news_board_t *b = g_news->boardlist[i];
			if (NULL == b)
				continue;
			/* skip boards that we already have */
			if (NULL != strstr(g_conf->news_boards, b->board))
				continue;
			/* skip boards that we are told to ignore */
			if (NULL != strstr(g_conf->news_ignore, b->board))
				continue;

			announcements[j++] = b;
		}

		if (0 == j) {
			OUT2("%s", LO("none yet"));
		} else {
			qsort(announcements, j, sizeof(news_board_t *),
				sort_boards_by_name);

			OUT1("     <form method=\"GET\" action=\"/news.boards\">\n");
			OUT1("      <select name=\"board\" size=\"1\">\n");
			while (j > 0) {
				news_board_t *b = announcements[--j];
				if (0 != strcmp(last, b->board)) {
					if ('\0' != *last) {
						OUT1("</option>\n");
					}
					OUT3("       <option value=\"%s\">%s</option>\n",
						b->board, b->board);
					last = b->board;
				}
				OUT5("        <option value=\"%s\">&nbsp;&nbsp;%04d-%02d-%02d</option>\n",
					b->board, 
					b->date.tm_year + 1900,
					b->date.tm_mon + 1,
					b->date.tm_mday);
			}
			OUT1("     </select>\n");
			OUT2("    <input type=\"submit\" name=\"submit\" value=\"%s\" />\n",
				LO("subscribe"));
			OUT2("    <input type=\"submit\" name=\"submit\" value=\"%s\" />\n",
				LO("ignore"));
			OUT1("    </form>\n");
		}
		xfree(announcements);
	}

	OUT1("    </td>\n");
	OUT1("   </tr>\n");
	OUT1("   <tr>\n");
	OUT1("    <td colspan=\"9\"><br /></td>\n");
	OUT1("   </tr>\n");
	OUT1("  </table>\n");


	if (NULL != exp_board) {
		char prev[128], next[128];
		OUT1("  <!-- messages -->\n");
		OUT1("  <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");
		OUT1("   <colgroup>\n");
		OUT1("    <col width=\"5%%\">\n");
		OUT1("    <col width=\"5%%\">\n");
		OUT1("    <col width=\"10%%\">\n");
		OUT1("    <col width=\"30%%\">\n");
		OUT1("    <col width=\"10%%\">\n");
		OUT1("    <col width=\"10%%\">\n");
		OUT1("    <col width=\"10%%\">\n");
		OUT1("    <col width=\"20%%\">\n");
		OUT1("   </colgroup>\n");

		for (i = 0, j = 0; i < g_news->entry_cnt; i++) {
			news_entry_t *e = g_news->entrylist[i];
			if (NULL == e)
				continue;

			/* skip if we filter boards and the board name does not match */
			if (0 != strcmp(exp_board, "*") &&
				NULL != e->board &&
				0 != strcasecmp(exp_board, e->board)) {
				continue;
			}
			/* also skip if we filter by date and the date does not match */
			if (0 != exp_tm.tm_year &&
				(exp_tm.tm_year != e->date.tm_year ||
				exp_tm.tm_mon != e->date.tm_mon ||
				exp_tm.tm_mday != e->date.tm_mday)) {
				continue;
			}
			/* also skip if we filter new messages and this messages was seen */
			if (0 != exp_new && 0 != e->seen) {
				continue;
			}

			if (j == 0) {
				pm_snprintf(prev, sizeof(prev), "<a href=\"#top\"><img src=\"/node/prev.png\" width=\"24\" height=\"12\" border=\"0\" alt=\"{m%d}\" /></a>", j-1);
			} else {
				pm_snprintf(prev, sizeof(prev), "<a href=\"#m%d\"><img src=\"/node/prev.png\" width=\"24\" height=\"12\" border=\"0\" alt=\"{m%d}\" /></a>", j-1, j-1);
			}
			pm_snprintf(next, sizeof(next), "<a href=\"#m%d\"><img src=\"/node/next.png\" width=\"24\" height=\"12\" border=\"0\" alt=\"{m%d}\" /></a>", j+1, j+1);

			OUT1("   <tr class=\"title\">\n");

			/* CHK@ of message */
			OUT2("    <th class=\"label\">%s</th>\n",
				LO("msg"));
			OUT6("    <td class=\"content\"><a name=\"m%d\"><a href=\"/%s?mime=text%%2fplain\">%s#%d%s</a></a></td>\n",
				j,
				key_long(&e->key),
				0 == e->seen ? "<span class=\"hilite\">" : "",
				e->slot,
				0 == e->seen ? "</span>" : "");
			e->seen = 1;
			j++;

			/* from */
			OUT2("    <th class=\"label\">%s</th>\n",
				LO("from"));
			if (0 != strlen(e->from)) {
				OUT2("    <td class=\"content\">%s</td>\n",
					e->from);
			} else {
				OUT2("    <td class=\"content\">%s</td>\n",
					LO("empty"));
			}

			/* board */
			OUT2("    <th class=\"label\">%s</th>\n",
				LO("board"));
			if (0 != strlen(e->board)) {
				OUT2("    <td class=\"content\">%s</td>\n",
					e->board);
			} else {
				OUT2("    <td class=\"content\">%s</td>\n",
					LO("empty"));
			}

			/* date */
			OUT2("    <th class=\"label\">%s</th>\n",
				LO("date"));
			OUT3("    <td class=\"content\">%s%s</td>\n",
				datetime_str(e->time), LO("GMT"));

			OUT1("   </tr>\n");

			/* 2nd row */	
			OUT1("   <tr class=\"title\">\n");

			OUT3("    <th class=\"label\">%s%s</th>\n",
				prev, next);

			OUT3("    <th class=\"button\"><a href=\"/news.reply?chk=%s\">%s</a></th>\n",
				key_long(&e->key),
				LO("reply"));

			/* subject */
			OUT2("    <th class=\"label\">%s</th>\n",
				LO("subject"));
			if (0 != strlen(e->subject)) {
				OUT2("    <td class=\"content\" colspan=\"5\"><b>%s</b></td>\n",
					e->subject);
			} else {
				OUT2("    <td class=\"content\" colspan=\"5\">%s</td>\n",
					LO("empty"));
			}

			OUT1("   </tr>\n");
			OUT1("   <tr class=\"body\">\n");
			if (NULL != e->body && strlen(e->body) > 0) {
				char *body = proxy_news_body(conn, e->body);
				OUT2("    <td class=\"message\" colspan=\"8\">%s</td>",
					body);
				xfree(body);
			} else {
				OUT2("    <td class=\"message\" colspan=\"8\">%s</td>\n",
					LO("empty"));
			}
			OUT1("   </tr>\n");
			OUT1("   <tr>\n");
			OUT1("    <td colspan=\"8\"><br /></td>\n");
			OUT1("   </tr>\n");
		}
		OUT1("  </table>\n");
	}

	osd_sem_post(&g_news->sem);

	OUT1(" </body>\n");
	OUT1("</html>\n");
	
	xfree(boards);
	xfree(exp_board);
	
	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	LOGS(L_PROXY,L_DEBUG,("used %u bytes for %d news\n",
		(unsigned)reply->content_length,
		(int)g_news->entry_cnt));
	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_news_create()
 *	Create a new message for a board
 */
static int proxy_news_create(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	proxy_charset_t *charset;
	size_t i, max = 4096;
	char *dst, *msg = NULL;
	time_t t0;
	int rc = 0;
	FUN("proxy_news_create");

	url += strlen("news.create");

	t0 = time(NULL);
	memcpy(&reply->date, gmtime(&t0), sizeof(reply->date));

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("News create"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", "%s", LO("News create")));

	OUT1("  <form method=\"get\" action=\"/news.post\">\n");
	OUT1("  <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");
	OUT1("   <colgroup>\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"90%%\">\n");
	OUT1("   </colgroup>\n");

	/* reply board */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("board"));
	OUT2("    <td class=\"content\"><input type=\"text\" name=\"board\" value=\"%s\" size=\"80\" maxlength=\"255\" readonly=\"readonly\" /></td>\n",
		reply->board);
	OUT1("   </tr>\n");

	/* reply date and time */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("date, time"));
	OUT8("    <td class=\"content\"><input type=\"text\" name=\"date\" value=\"%04d.%02d.%02d\" size=\"10\" maxlength=\"10\" readonly=\"readonly\" />, <input type=\"text\" name=\"time\" value=\"%02d:%02d:%02d%s\" size=\"11\" maxlength=\"11\" readonly=\"readonly\" /></td>\n",
		reply->date.tm_year + 1900,
		reply->date.tm_mon + 1,
		reply->date.tm_mday,
		reply->date.tm_hour,
		reply->date.tm_min,
		reply->date.tm_sec,
		LO("GMT"));
	OUT1("   </tr>\n");

	/* reply subject */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("subject"));
	OUT2("    <td class=\"content\"><input type=\"text\" name=\"subject\" value=\"%s\" size=\"80\" maxlength=\"255\" /></td>\n",
		"No subject");
	OUT1("   </tr>\n");

	/* reply from */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("from"));
	OUT2("    <td class=\"content\"><input type=\"text\" name=\"from\" value=\"%s\" size=\"80\" maxlength=\"255\" /></td>\n",
		g_conf->news_nick);
	OUT1("   </tr>\n");

	/* reply charset  */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("charset"));
	OUT1("    <td class=\"content\">\n");
	OUT1("     <select name=\"charset\" size=\"1\">\n");
	for (i = 0; i < sizeof(charsets)/sizeof(charset[0]); i++) {
		charset = &charsets[i];
		OUT4("      <option value=\"%s\"%s>%s</option>\n",
			charset->name, "", charset->name);
	}
	OUT1("     </select>\n");
	OUT1("    </td>\n");
	OUT1("   </tr>\n");

	/* reply message */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("message"));
	OUT2("    <td class=\"content\"><textarea name=\"body\" cols=\"80\" rows=\"16\">%s</textarea></td>\n",
		LO("Type your message here..."));
	OUT1("   </tr>\n");

	/* submit button */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></td>\n",
		LO("submit"));
	OUT1("   </tr>\n");
	OUT1("  </table>\n");
	OUT1("  </form>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_news_reply()
 *	Create a reply message to a CHK@
 */
static int proxy_news_reply(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	news_entry_t *e = NULL;
	size_t max = 4096;
	char *dst, *msg = NULL;
	struct tm tm;
	size_t i;
	time_t t0;
	int rc = 0;
	FUN("proxy_news_reply");

	url += strlen("news.reply");

	if (0 != osd_sem_wait(&g_news->sem)) {
		return -1;
	}

	t0 = time(NULL);
	memcpy(&reply->date, gmtime(&t0), sizeof(reply->date));

	for (i = 0; i < g_news->entry_cnt; i++) {
		e = g_news->entrylist[i];
		if (0 == memcmp(&e->key.sha1, &reply->key.sha1, SHA1SIZE))
			break;
	}

	if (i == g_news->entry_cnt) {
		osd_sem_post(&g_news->sem);
		return proxy_403(conn, url);
	}

	/* set the charset for the reply HTML code to the one from the message */
	if (NULL != e->charset) {
		xfree(reply->charset);
		reply->charset = xstrdup(e->charset);
	}

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("News reply"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("News reply")));

	OUT1("  <form method=\"get\" action=\"/news.post\">\n");
	OUT1("  <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");
	OUT1("   <colgroup>\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"90%%\">\n");
	OUT1("   </colgroup>\n");

	/* reply board */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("board"));
	OUT2("    <td class=\"content\"><input type=\"text\" name=\"board\" value=\"%s\" size=\"80\" maxlength=\"255\" readonly=\"readonly\" /></td>\n",
		e->board);
	OUT1("   </tr>\n");

	/* reply date and time */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("date, time"));
	OUT8("    <td class=\"content\"><input type=\"text\" name=\"date\" value=\"%04d.%02d.%02d\" size=\"10\" maxlength=\"10\" readonly=\"readonly\" />, <input type=\"text\" name=\"time\" value=\"%02d:%02d:%02d%s\" size=\"11\" maxlength=\"11\" readonly=\"readonly\" /></td>\n",
		reply->date.tm_year + 1900,
		reply->date.tm_mon + 1,
		reply->date.tm_mday,
		reply->date.tm_hour,
		reply->date.tm_min,
		reply->date.tm_sec,
		LO("GMT"));
	OUT1("   </tr>\n");

	/* reply subject */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("subject"));
	OUT3("    <td class=\"content\"><input type=\"text\" name=\"subject\" value=\"%s%s\" size=\"80\" maxlength=\"255\" /></td>\n",
		0 == strncmp(e->subject, "Re:", 3) ? "" : "Re: ",
		e->subject);
	OUT1("   </tr>\n");

	/* reply from */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("from"));
	OUT2("    <td class=\"content\"><input type=\"text\" name=\"from\" value=\"%s\" size=\"80\" maxlength=\"255\" /></td>\n",
		g_conf->news_nick);
	OUT1("   </tr>\n");

	/* reply charset */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("charset"));
	OUT3("    <td class=\"content\"><input type=\"hidden\" name=\"charset\" value=\"%s\" size=\"16\"/>%s</td>\n",
		NULL != e->charset ? e->charset : "iso-8859-1",
		NULL != e->charset ? e->charset : "iso-8859-1");
	OUT1("   </tr>\n");

	/* reply message */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("message"));
	OUT2("    <td class=\"content\"><textarea name=\"body\" cols=\"80\" rows=\"16\">",
		e->body);
	memcpy(&tm, gmtime(&e->time), sizeof(tm));
	if (0 != strncmp(e->body, "-----", 5)) {
		OUT9("----- %s ----- %04d.%02d.%02d - %02d:%02d:%02d%s -----\n\n",
			e->from,
			tm.tm_year + 1900,
			tm.tm_mon + 1,
			tm.tm_mday,
			tm.tm_hour,
			tm.tm_min,
			tm.tm_sec,
			LO("GMT"));
	}
	OUT2("%s\n",
		e->body);
	OUT9("----- %s ----- %04d.%02d.%02d - %02d:%02d:%02d%s -----\n\n",
		g_conf->news_nick,
		reply->date.tm_year + 1900,
		reply->date.tm_mon + 1,
		reply->date.tm_mday,
		reply->date.tm_hour,
		reply->date.tm_min,
		reply->date.tm_sec,
		LO("GMT"));
	OUT1("</textarea></td>\n");
	OUT1("   </tr>\n");

	/* submit button */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></th>\n",
		LO("submit"));
	OUT1("   </tr>\n");
	OUT1("  </table>\n");
	OUT1("  </form>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	/* release the lock on news */
	osd_sem_post(&g_news->sem);

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_news_boards()
 *	List, add or ignore a board
 */
static int proxy_news_boards(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096, i, j, len;
	char *dst, *msg = NULL;
	time_t t0;
	int rc = 0;
	FUN("proxy_news_boards");

	url += strlen("news.boards");

	t0 = time(NULL);
	memcpy(&reply->date, gmtime(&t0), sizeof(reply->date));

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("news boards"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", "%s", LO("News boards")));

	OUT1("  <form method=\"get\" action=\"/news\">\n");
	OUT1("  <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");
	OUT1("   <colgroup>\n");
	OUT1("    <col width=\"10%%\">\n");
	OUT1("    <col width=\"90%%\">\n");
	OUT1("   </colgroup>\n");

	/* reply board */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("board"));
	OUT2("    <td class=\"content\">%s</td>\n",
		reply->board);
	OUT1("   </tr>\n");

	if (0 == strcasecmp(reply->submit, LO("subscribe"))) {
		char *news_boards = NULL;
		/* add board to news_boards= */
		if (0 == strlen(g_conf->news_boards)) {
			len = pm_asprintf(&news_boards, "%s", reply->board);
		} else {
			len = pm_asprintf(&news_boards, "%s,%s",
				g_conf->news_boards, reply->board);
		}
		OUT1("   <tr class=\"head\">\n");
		OUT2("    <th class=\"label\">%s</th>\n",
			LO("news_boards="));
		OUT2("    <td class=\"content\"><textarea name=\"news_boards\" cols=\"80\" rows=\"3\" readonly=\"readonly\" />%s</textarea></td>\n",
			news_boards);
		OUT1("   </tr>\n");
		xfree(news_boards);
	} else if (0 == strcasecmp(reply->submit, LO("unsubscribe"))) {
		char *news_boards = NULL;
		/* subtract board from news_boards= */
		len = pm_asprintf(&news_boards, "%s",
			g_conf->news_boards);
		j = strlen(reply->board);
		for (i = 0; i < len; i++) {
			if (0 == strncmp(&news_boards[i], reply->board, j)) {
				if (news_boards[i+j] == ',')
					strcpy(&news_boards[i], &news_boards[i+j+1]);
				else if (i > 0 && news_boards[i-1] == ',')
					news_boards[i-1] = '\0';
				else
					news_boards[0] = '\0';
				break;
			}
		}
		OUT1("   <tr class=\"head\">\n");
		OUT2("    <th class=\"label\">%s</th>\n",
			LO("news_boards="));
		OUT2("    <td class=\"content\"><textarea name=\"news_boards\" cols=\"80\" rows=\"3\" readonly=\"readonly\" />%s</textarea></td>\n",
			news_boards);
		OUT1("   </tr>\n");
		xfree(news_boards);
	} else if (0 == strcasecmp(reply->submit, LO("ignore"))) {
		char *news_ignore = NULL;
		/* add board to news_ignore= */
		if (0 == strlen(g_conf->news_ignore)) {
			len = pm_asprintf(&news_ignore, "%s", reply->board);
		} else {
			len = pm_asprintf(&news_ignore, "%s,%s",
				g_conf->news_ignore, reply->board);
		}
		OUT1("   <tr class=\"head\">\n");
		OUT2("    <th class=\"label\">%s</th>\n",
			LO("news_ignore="));
		OUT2("    <td class=\"content\"><textarea name=\"news_ignore\" cols=\"80\" rows=\"3\" readonly=\"readonly\" />%s</textarea></td>\n",
			news_ignore);
		OUT1("   </tr>\n");
		xfree(news_ignore);
	}

	/* password for confirmation */
	OUT1("   <tr class=\"head\">\n");
	OUT2("    <th class=\"label\">%s</th>\n",
		LO("password"));
	OUT1("    <td class=\"content\"><input type=\"password\" name=\"password\" value=\"\" size=\"16\" maxlength=\"16\" /></td>\n");
	OUT1("   </tr>\n");

	OUT1("   <tr class=\"head\">\n");

	/* submit button */
	if (0 == strcasecmp(reply->submit, LO("subscribe"))) {
		OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></td>\n",
			LO("add board"));
	} else if (0 == strcasecmp(reply->submit, LO("unsubscribe"))) {
		OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></td>\n",
			LO("sub board"));
	} else if (0 == strcasecmp(reply->submit, LO("ignore"))) {
		OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></td>\n",
			LO("ignore board"));
	} else {
		OUT2("    <td class=\"button\" colspan=\"2\"><input type=\"submit\" name=\"submit\" value=\"%s\" /></td>\n",
			reply->submit);
	}
	OUT1("   </tr>\n");
	OUT1("  </table>\n");
	OUT1("  </form>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_news_post()
 *	Post a reply message; insert data under a KSK
 */
static int proxy_news_post(conn_t *conn, const char *url)
{
	proxy_reply_t *reply = conn->temp;
	news_post_t *np = reply->news_post;
	size_t max = 4096;
	char *src, *dst, *msg = NULL;
	int x0, y0, x1, y1, rc = 0;
	FUN("proxy_news_post");

	if (INVALID_ADDR == (caddr_t)np) {
		LOGS(L_PROXY,L_ERROR,("np is INVALID_ADDR\n"));
		reply->news_post = NULL;
		return proxy_403(conn, url);
	}

	url += strlen("news.post");

	if (NULL == np) {
		char *data = NULL;

		reply->news_post = np =
			(news_post_t *)scalloc(1, sizeof(news_post_t));

		if (NULL != np) {
			/* set news_post PID to zero */
			spset((caddr_t)np, 0);
			dst = data = scalloc(max, sizeof(char));
			if (NULL != data) {
				dst += pm_spprintf(dst,data,max, "board=%s\r\n",
					reply->board ? reply->board : "empty");
				dst += pm_spprintf(dst,data,max, "from=%s\r\n",
					reply->from ? reply->from : "Anonymous");
				dst += pm_spprintf(dst,data,max, "subject=%s\r\n",
					reply->subject ? reply->subject : "No subject");
				dst += pm_spprintf(dst,data,max, "date=%04d.%02d.%02d\r\n",
					reply->date.tm_year + 1900,
					reply->date.tm_mon + 1,
					reply->date.tm_mday);
				dst += pm_spprintf(dst,data,max, "time=%02d:%02d:%02d%s\r\n",
					reply->date.tm_hour,
					reply->date.tm_min,
					reply->date.tm_sec,
					LO("GMT"));
				if (NULL != reply->charset) {
					dst += pm_spprintf(dst,data,max, "charset=%s\r\n",
						reply->charset);
				}
				dst += pm_spprintf(dst,data,max, "--- message ---\r\n");
				for (src = reply->body; *src; src++) {
					if (*src != '\r')
						*dst++ = *src;
					if (dst >= &data[max-1])
						break;
				}
				*dst = '\0';
				np->board = sstrdup(reply->board);
				np->charset = sstrdup(reply->charset);
				np->date = reply->date;
				np->data = data;
				np->rc = RESULT_CREATED;
			}
		}
	}

	xfree(reply->charset);
	reply->charset = xstrdup("utf-8");

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("News post"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("News post")));

	OUT1("  <div align=\"center\">\n");
	OUT1("   <table width=\"100%%\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\">\n");

	if (0 != svalid(np)) {
		OUT1("    <tr>\n");
		OUT2("     <td class=\"error\">%s</td>\n",
			LO("Failed to allocate shared memory to post news."));
		OUT1("    </tr>\n");
		reply->news_post = NULL;
	} else {
		if (RESULT_SUCCESS == np->rc) {
			char *fquri = np->fquri;
			if (0 == strncmp(fquri, "entropy:", 8)) {
				fquri += 8;
			}
			OUT1("    <tr>\n");
			OUT4("     <td class=\"subtitle\">%s <a href=\"/%s?mime=text%%2fplain\">%s</a></td>\n",
				LO("Successfully inserted message"),
				fquri, fquri);
			OUT1("    </tr>\n");
		} else if (RESULT_ERROR == np->rc) {
			OUT1("    <tr>\n");
			OUT2("     <td class=\"error\">%s</td>\n",
				LO("Failed to insert message."));
			OUT1("    </tr>\n");
		}
	}

	OUT1("    <tr>\n");
	/* dimensions of the virtual terminal (depends on font width&height!) */
#undef	FW
#undef	FH
#define	FW	8
#define	FH	12
	x0 = 4;
	x1 = 4 + 80 * FW - 1;
	y0 = 4;
	y1 = 4 + FH - 1;
	OUT1("      <td class=\"center\">\n");
	OUT1("        <map name=\"imagemap\">\n");
	OUT6("          <area shape=\"rect\" coords=\"%d,%d,%d,%d\" href=\"/news.post?news_post=%p\" />\n",
		x0, y0, x1, y1, np);
	OUT1("        </map>\n");
	OUT4("        <img src=\"/node/vt_news_post.gif?news_post=%p\" usemap=\"#imagemap\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"news_post\" />\n",
		np, 80 * FW + 8, 24 * FH + 8);
	OUT1("      </td>\n");
	OUT1("    </tr>\n");
	OUT1("   </table>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	if (0 == svalid(np) &&
		RESULT_CREATED != np->rc &&
		RESULT_PENDING != np->rc) {
		if (0 == svalid(np->board))
			sfree((caddr_t)np->board);
		if (0 == svalid(np->charset))
			sfree((caddr_t)np->charset);
		if (0 == svalid(np->data))
			sfree((caddr_t)np->data);
		sfree((caddr_t)np);
		reply->news_post = NULL;
	}
	xfree(msg);
	return rc;
}

/*
 *	proxy_checked()
 *	External reference via /__CHECKED_FTP__
 *	External reference via /__CHECKED_GOPHER__
 *	External reference via /__CHECKED_HTTP__
 *	External reference via /__CHECKED_HTTPS__
 *	External reference via /__CHECKED_RTSP__
 */
static int proxy_checked(conn_t *conn, const char *url, const char *proto)
{
	proxy_reply_t *reply = conn->temp;
	size_t max = 4096;
	char *dst, *msg = NULL;
	char *decoded = NULL;
	int rc = 0;
	FUN("proxy_ftp");

	url += strlen("__CHECKED___") + strlen(proto);
	url_decode(&decoded, url);

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT4("  <title>%s %s (%s)</title>\n",
		VERSION,
		LO("External reference"),
		url);
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "warning", LO("External reference")));
	OUT4("  <h3>%s %s %s</h3>\n",
		LO("The HREF you clicked leads to a location on some"),
		proto,
		LO("server"));
	OUT2("  <p>%s</p>\n",
		LO("There is nothing wrong with that. This page is to warn you that you are about to leave the ENTROPY network now."));
	OUT6("  <p class=\"hilite\">%s <a href=\"%s://%s\" target=\"_top\">%s://%s</a></p>\n",
		LO("This means your anonymity is void:"),
		proto, decoded, proto, decoded);
	OUT1(" </body>\n");
	OUT1("</html>\n");
	xfree(decoded);

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);

	xfree(msg);
	return rc;
}

/*
 *	proxy_insert()
 *	Insert a chunk of data, gathered from a HTTP POST header, by connecting
 *	to the FCP interface on this node and sending the data there.
 */
static int proxy_insert(conn_t *conn, const char *url, char *tmpfile,
	proxy_post_var_t *var, size_t var_cnt)
{
	proxy_reply_t *reply = conn->temp;
	file_up_t *up = reply->file_up;
	size_t n, max = 4096;
	char *dst, *msg = NULL;
	int x0, y0, x1, y1, rc = 0;
	FUN("proxy_insert");

	if (INVALID_ADDR == (caddr_t)up) {
		LOG(L_ERROR,("up is INVALID_ADDR\n"));
		reply->file_up = NULL;
		return proxy_403(conn, url);
	}

	if (NULL == up && NULL != var && 0 != var_cnt) {

		LOG(L_DEBUG,("allocationg file_up_t\n"));
		/* allocate a file upload shm structure */
		up = reply->file_up =
			(file_up_t *)scalloc(1, sizeof(file_up_t));

		LOG(L_DEBUG,("file_up:%p\n", up));

		if (0 == svalid(up)) {
			LOG(L_DEBUG,("setting file_up:%p PID to zero\n", up));
			/* set news_post PID to zero */
			spset((caddr_t)up, 0);

			/* set the temp filename */
			pm_snprintf(up->tmpfile, sizeof(up->tmpfile), "%s",
				tmpfile);

			/* default content-type: auto detect */
			up->content_type = sstrdup("auto");

			/* default filename: dummy.bin */
			up->filename = sstrdup("dummy.bin");

			for (n = 0; n < var_cnt; n++) {
				proxy_post_var_t *curr = &var[n];
				if (0 == strcasecmp("key", curr->name)) {
					if (0 == svalid(up->key))
						sfree(up->key);
					if (strlen(curr->value) > 0) {
						up->key = sstrdup(curr->value);
					} else {
						up->key = sstrdup("CHK@");
					}
					LOGS(L_PROXY,L_MINOR,("%s=%s\n", curr->name, up->key));
				} else if (0 == strcasecmp("htl", curr->name)) {
					if (strlen(curr->value) > 0) {
						up->htl = strtol(curr->value, NULL, 0);
					} else {
						up->htl = 2;
					}
					LOGS(L_PROXY,L_MINOR,("%s=%x\n", curr->name, up->htl));
				} else if (0 == strcasecmp("content-type", curr->name)) {
					if (0 == svalid(up->content_type))
						sfree(up->content_type);
					if (strlen(curr->value) > 0) {
						up->content_type = sstrdup(curr->value);
					} else {
						up->content_type = sstrdup("auto");
					}
					LOGS(L_PROXY,L_MINOR,("%s=%s\n",
						curr->name, up->content_type));
				} else if (0 == strcasecmp("data", curr->name) ||
							0 == strcasecmp("file", curr->name) ||
							0 == strcasecmp("content", curr->name) ||
							0 == strcasecmp("filename", curr->name)) {
					up->dataoffs = curr->offs;
					up->datasize = curr->size;
					if (strlen(curr->content_type) > 0) {
						if (0 == svalid(up->content_type))
							sfree(up->content_type);
						up->content_type = sstrdup(curr->content_type);
					}
					if (strlen(curr->filename) > 0) {
						if (0 == svalid(up->content_type))
							sfree(up->filename);
						up->filename = sstrdup(curr->filename);
					}
					LOGS(L_PROXY,L_MINOR,("%s=%#x,+%#x\n",
						curr->name,
						(unsigned)up->dataoffs,
						(unsigned)up->datasize));
				} else if (NULL != curr->name && strlen(curr->name) > 0) {
					LOGS(L_PROXY,L_MINOR,("ignored %s @ %#x size %#x\n",
						curr->name,
						(unsigned)curr->offs,
						(unsigned)curr->size));
				}
			}

			if (NULL == up->content_type) {
				up->content_type = sstrdup(auto_mime_type(up->filename));
				LOGS(L_PROXY,L_MINOR,("use content-type: %s\n",
					up->content_type));
			} else if (0 == strcasecmp(up->content_type, "auto")) {
				sfree(up->content_type);
				up->content_type = sstrdup(auto_mime_type(up->filename));
				LOGS(L_PROXY,L_MINOR,("set content-type: %s\n",
					up->content_type));
			}

			/* set the 'created' result code */
			LOGS(L_PROXY,L_MINOR,("set up->rc to RESULT_CREATED\n"));
			up->rc = RESULT_CREATED;
		}
	}

	dst = msg = xcalloc(max, sizeof(char));

	OUT1(DOCTYPE);
	OUT1("<html>\n");
	OUT1(" <head>\n");
	OUT3("  <title>%s %s</title>\n",
		VERSION,
		LO("File insert"));
	OUT1(CONTENT_TYPE);
	OUT1(STYLESHEET);
	OUT1(" </head>\n");
	OUT1(" <body>\n");
	OUT2("  %s\n", nav(reply, "subtitle", LO("File insert")));
	OUT1("  <div align=\"center\">\n");
	OUT1("   <table>\n");

	if (0 != svalid(up)) {
		OUT1("    <tr>\n");
		OUT2("     <td class=\"error\">%s</td>\n",
			LO("Failed to allocate shared memory to insert data."));
		OUT1("    </tr>\n");
	} else {
		if (RESULT_SUCCESS == up->rc) {
			char *fquri = up->fquri;
			if (0 == strncmp(fquri, "entropy:", 8)) {
				fquri += 8;
			}
			OUT1("    <tr>\n");
			OUT4("     <td class=\"subtitle\">%s <a href=\"/%s\">%s</a></td>\n",
				LO("Successfully inserted data"), fquri, fquri);
			OUT1("    </tr>\n");
		} else if (RESULT_ERROR == up->rc) {
			OUT1("    <tr>\n");
			OUT3("     <td class=\"error\">%s (%d)</td>\n",
				LO("Failed to insert data."), up->rc);
			OUT1("    </tr>\n");
		}
	}

	OUT1("    <tr>\n");
	/* dimensions of the virtual terminal (depends on font width&height!) */
#undef	FW
#undef	FH
#define	FW	8
#define	FH	12
	x0 = 4;
	x1 = 4 + 80 * FW - 1;
	y0 = 4;
	y1 = 4 + FH - 1;
	OUT1("     <td class=\"center\">\n");
	OUT1("       <map name=\"file_up\">\n");
	OUT6("         <area shape=\"rect\" coords=\"%d,%d,%d,%d\" href=\"/proxy.insert?file_up=%p\" />\n",
		x0, y0, x1, y1, up);
	OUT1("       </map>\n");
	OUT4("       <img src=\"/node/vt_file_up.gif?file_up=%p\" usemap=\"#file_up\" width=\"%d\" height=\"%d\" border=\"0\" alt=\"file_up\" />\n",
		up, 80 * FW + 8, 24 * FH + 8);
	OUT1("     </td>\n");
	OUT1("    </tr>\n");
	OUT1("   </table>\n");
	OUT1("  </div>\n");
	OUT1(" </body>\n");
	OUT1("</html>\n");

	reply->result_code = 200;
	reply->no_cache = 1;
	xfree(reply->content_type);
	reply->content_type = xstrdup("text/html");
	reply->passthru = pass_str;

	html_vars(reply, &msg);
	rc = proxy_reply(conn);
	xfree(msg);

	osd_sleep(2);
	
	if (0 == svalid(up) &&
		RESULT_CREATED != up->rc &&
		RESULT_PENDING != up->rc) {
		LOGS(L_PROXY,L_MINOR,("rc is not RESULT_CREATED or RESULT_PENDING\n"));
		if (0 == svalid(up->key))
			sfree((caddr_t)up->key);
		if (0 == svalid(up->content_type))
			sfree((caddr_t)up->content_type);
		if (0 == svalid(up->filename))
			sfree((caddr_t)up->filename);
		sfree((caddr_t)up);
		reply->file_up = NULL;
	}
	return rc;
}

/*
 *	strip_quote()
 *	Remove double quotes from a string and return the 'contents'
 */
static int strip_quote(char *dst, const char *src, size_t maxlen)
{
	char *eob = &dst[maxlen];
	size_t len;

	len = 0;
	while (*src == ' ') {
		src++;
		len++;
	}
	if (*src == '"') {
		src++;
		len++;
	}
	while (*src != '\0' && *src != '"' && *src != '\r' && *src != '\n') {
		if (dst < eob) {
			*dst++ = *src;
		}
		src++;
		len++;
	}
	*dst = '\0';
	if (*src == '"') {
		src++;
		len++;
	}

	return len;
}

enum {
	SCAN_BOUNDARY,
	SCAN_VARIABLE,
	SCAN_VALUE
};

static int proxy_post(conn_t *conn, const char *url)
{
	FILE *fp;
	char *tmpfile = xcalloc(MAXPATHLEN, sizeof(char));
	proxy_reply_t *reply = conn->temp;
	char *buff = NULL;
	char *src, *tag;
	char *line = NULL;
	char *scan = NULL;
	char *post_boundary = NULL;
	proxy_post_var_t *var = NULL, *prev, *curr;
	size_t var_len, var_cnt = 0, var_max = 8;
	size_t fcp_blocksize, part, n;
	size_t len, offs = 0, boundary_len;
	int state, rc = 0;
	FUN("proxy_post");

	var_max = 8;
	var = (proxy_post_var_t *)xcalloc(var_max, sizeof(proxy_post_var_t));

	pm_snprintf(tmpfile, MAXPATHLEN, "%s/%s-post.%d",
		g_conf->temppath, g_conf->progname, getpid());

	post_boundary = strstr(reply->content_type, "boundary=");
	if (NULL == post_boundary) {
		rc = proxy_400(conn, LO("No 'boundary=' found in 'Content-type:'"));
		goto bailout;
	}
	post_boundary = xstrdup(post_boundary + strlen("boundary="));
	boundary_len = strlen(post_boundary);

	if (NULL == (fp = fopen(tmpfile, "wb"))) {
		LOGS(L_PROXY,L_ERROR,("fopen('%s','%s') call failed (%s)\n",
			tmpfile, "wb", strerror(errno)));
		rc = proxy_400(conn,
			LO("Could not create temporary file for POST data"));
		goto bailout;
	}

	line = xcalloc(LINESIZE, sizeof(char));
	scan = xcalloc(LINESIZE, sizeof(char));

	fcp_blocksize = (conn->maxsize < FCP_BLOCKSIZE) ?
		conn->maxsize : FCP_BLOCKSIZE;
	buff = xcalloc(fcp_blocksize + 1, sizeof(char));
	LOGS(L_PROXY,L_MINOR,("reading %#x bytes in chunks of %#x bytes\n",
		(unsigned)reply->content_length, (unsigned)fcp_blocksize));

	len = 0;
	state = SCAN_BOUNDARY;
	for (offs = 0, n = 0; offs < reply->content_length; n++) {
		part = (offs + fcp_blocksize > reply->content_length) ?
			reply->content_length - offs : fcp_blocksize;
		LOGS(L_PROXY,L_MINOR,("reading %#x @ %#x\n",
			(unsigned)part, (unsigned)offs));
		if (0 != (rc = sock_readall(conn, buff, part))) {
			LOGS(L_PROXY,L_ERROR,("sock_readall(%#x) call failed (%s)\n",
				(unsigned)part, strerror(errno)));
			rc = proxy_400(conn,
				LO("Error reading POST data from browser"));
			goto bailout;
		}

		/* scan for boundaries */
		for (src = buff; src < &buff[part]; src++) {
			line[len] = *src;
			if (len < LINESIZE - 1) {
				len++;
			}
			if (*src != '\n')
				continue;
			line[len--] = '\0';
			if (line[len] == '\r')
				line[len--] = '\0';
			len = 0;
			switch (state) {
			case SCAN_BOUNDARY:
				if (NULL != strstr(line, post_boundary)) {
					if (var_cnt > 0) {
						prev = &var[var_cnt - 1];
						prev->size = offs + (size_t)(src + 1 - buff) -
							prev->offs - 2 - boundary_len - 4;
						LOGS(L_PROXY,L_MINOR,("name:'%s' offs:%#x size:%#x\n",
							prev->name,
							(unsigned)prev->offs,
							(unsigned)prev->size));
					}
					if (var_cnt >= var_max) {
						var_max *= 2;
						var = (proxy_post_var_t *)xrealloc(var,
							var_max * sizeof(proxy_post_var_t));
					}
					curr = &var[var_cnt];
					var_cnt++;
					memset(curr, 0, sizeof(*curr));
					state = SCAN_VARIABLE;
					LOGS(L_PROXY,L_MINOR,("state SCAN_VARIABLE\n"));
				}
				break;
			case SCAN_VARIABLE:
				if (var_cnt < 1)
					break;
				prev = &var[var_cnt - 1];
				var_len = strlen("Content-Disposition:");
				strncpy(scan, line, var_len+1);
				scan[var_len] = '\0';
				if (0 == strcasecmp(scan, "Content-Disposition:")) {
					var_len = strlen("name=");
					if (NULL != (tag = strstr(line, "name="))) {
						strip_quote(prev->name, tag+var_len,
							sizeof(prev->name));
						LOGS(L_PROXY,L_MINOR,("found name='%s'\n",
							prev->name));
					}
					var_len = strlen("filename=");
					if (NULL != (tag = strstr(line, "filename="))) {
						strip_quote(prev->filename, tag+var_len,
							sizeof(prev->filename));
						LOGS(L_PROXY,L_MINOR,("found filename='%s'\n",
							prev->filename));
					}
					break;
				}
				var_len = strlen("Content-Type:");
				strncpy(scan, line, var_len+1);
				scan[var_len] = '\0';
				if (0 == strcasecmp(scan, "Content-Type:")) {
					strip_quote(prev->content_type, line+var_len,
						sizeof(prev->content_type));
					LOGS(L_PROXY,L_MINOR,("found content-type='%s'\n",
						prev->content_type));
					break;
				}
				if (0 == strcmp(line, "\r\n")) {
					if (0 == strcasecmp(prev->name, "data") ||
						0 == strcasecmp(prev->name, "file") ||
						0 == strcasecmp(prev->name, "content") ||
						0 == strcasecmp(prev->name, "filename")) {
						state = SCAN_BOUNDARY;
						LOGS(L_PROXY,L_MINOR,("state SCAN_BOUNDARY\n"));
					} else {
						state = SCAN_VALUE;
						LOGS(L_PROXY,L_MINOR,("state SCAN_VALUE\n"));
					}
					prev->offs = offs + (size_t)(src + 1 - buff);
				} else {
					LOGS(L_PROXY,L_MINOR,("ignored line '%s'\n",
						line));
				}
				break;
			case SCAN_VALUE:
				if (var_cnt > 0) {
					prev = &var[var_cnt - 1];
					strip_quote(prev->value, line, sizeof(prev->value) - 1);
					LOGS(L_PROXY,L_MINOR,("found value='%s'\n",
						prev->value));
				}
				state = SCAN_BOUNDARY;
				break;
			}
		}

		if (part != fwrite(buff, 1, part, fp)) {
			LOGS(L_PROXY,L_ERROR,("fwrite(%#x) call failed (%s)\n",
				(unsigned)part, strerror(errno)));
			rc = proxy_400(conn,
				LO("Error writing POST data to temporary file"));
			goto bailout;
		}
		offs += part;
	}
	fclose(fp);
	rc = proxy_insert(conn, url, tmpfile, var, var_cnt);

bailout:
	xfree(buff);
	xfree(line);
	xfree(scan);
	xfree(post_boundary);
	xfree(tmpfile);
	return rc;
}

#define	MAXVARS	64
#define	L_CHILD	L_MINOR
static void proxy_child(conn_t *conn)
{
	proxy_reply_t *reply = NULL;
	char *line = NULL;
	char *toke = NULL;
	char *host = NULL;
	char *query = NULL;
	char *proto = NULL;
	char *redir = NULL;
	char *url = NULL;
	char *uri;	/* used to skip a leading slash */
	char *keys[MAXVARS], *vals[MAXVARS];
	size_t size;
	int i, sk, password_valid, rc;
	int keep_alive, keep_alive_count, keep_alive_timeout;
	fd_set rdfds;
	struct timeval tv;
	FUN("proxy_child");

	keep_alive_count = 5;	/* max 5. requests per connection */
	keep_alive_timeout = 3;	/* max 3. seconds between requests */
	do {
		keep_alive = 0;

		reply = (proxy_reply_t *)xcalloc(1, sizeof(proxy_reply_t));
		reply->htl = g_conf->proxyhtl;
		reply->request_type = NONE;
		strncpy(reply->language, g_conf->language, sizeof(reply->language));

		conn->temp = reply;

		while (0 == (rc = sock_agets(conn, &line))) {
			char *var, *val, *eol;
			toke = xstrdup(line);
			if (line[0] == '\r' || line[0] == '\n')
				break;
			var = toke;
			eol = strchr(var, '\r');
			if (NULL != eol) *eol = '\0';
			eol = strchr(var, '\n');
			if (NULL != eol) *eol = '\0';
			val = strchr(var, ':');
			if (NULL != val) {
				*val++ = '\0';
				while (*val) {
					if (*val != '\t' &&
						*val != '\r' &&
						*val != '\n' &&
						*val != ' ') break;
					val++;
				}
			}
			if (0 == strcasecmp(var,"From")) {
				LOGS(L_PROXY,L_CHILD,("<- From: %s\n", val));
			} else if (0 == strcasecmp(var,"Accept")) {
				LOGS(L_PROXY,L_CHILD,("<- Accept: %s\n", val));
			} else if (0 == strcasecmp(var,"Accept-Charset")) {
				LOGS(L_PROXY,L_CHILD,("<- Accept-Charset: %s\n", val));
			} else if (0 == strcasecmp(var,"Accept-Encoding")) {
				char *ptr;
				LOGS(L_PROXY,L_CHILD,("<- Accept-Encoding: %s\n", val));
				reply->accept_encoding = 0;
				/* find <space>gzip "..., gzip;q=0.5 ..." */
				ptr = strstr(val, " gzip");
				/* or <comma>gzip "...,gzip;q=0.5 ...*/
				if (NULL == ptr)
					ptr = strstr(val, ",gzip");
				/* or <colon>gzip "Accept-Encoding:gzip;q=0.5 ..." */
				if (NULL == ptr)
					ptr = strstr(val, ":gzip");
				if (NULL != ptr) {
					ptr += strlen(" gzip");
					/* skip any blanks after gzip */
					while (*ptr) {
						if (*ptr != '\t' &&
							*ptr != '\r' &&
							*ptr != '\n' &&
							*ptr != ' ') break;
						ptr++;
					}
					/* semicolon must follow now */
					if (*ptr == ';') {
						ptr++;
						/* skip more blanks */
						while (*ptr) {
							if (*ptr != '\t' &&
								*ptr != '\r' &&
								*ptr != '\n' &&
								*ptr != ' ') break;
							ptr++;
						}
						/* do we have a "q=" */
						if (0 == strncmp(ptr, "q=", 2)) {
							ptr += 2;
							/* find q=0 or q=0.0 or even q=0.0000 */
							while (*ptr == '0' || *ptr == '.')
								ptr++;
							/* use gzip if any non-zero digit is found */
							if (*ptr >= '1' && *ptr <= '9')
								reply->accept_encoding = 1;
						} else {
							/* without q= we assume q=1 */
							reply->accept_encoding = 1;
						}
					} else {
						/* without semicolon we assume q=1 */
						reply->accept_encoding = 1;
					}
				}
			} else if (0 == strcasecmp(var,"Accept-Language")) {
				LOGS(L_PROXY,L_CHILD,("<- Accept-Language: %s\n", val));
				if (0 == strncmp(val, "de", 2)) {
					pm_snprintf(reply->language, sizeof(reply->language), "de");
				} else if (0 == strncmp(val, "en", 2)) {
					pm_snprintf(reply->language, sizeof(reply->language), "en");
				}
			} else if (0 == strcasecmp(var,"User-Agent")) {
				LOGS(L_PROXY,L_CHILD,("<- User-Agent: %s\n", val));
			} else if (0 == strcasecmp(var,"Referer")) {
				LOGS(L_PROXY,L_CHILD,("<- Referer: %s\n", val));
			} else if (0 == strcasecmp(var,"Authorization")) {
				LOGS(L_PROXY,L_CHILD,("<- Authorization: %s\n", val));
			} else if (0 == strcasecmp(var,"Charge-To")) {
				LOGS(L_PROXY,L_CHILD,("<- Charge-To: %s\n", val));
			} else if (0 == strcasecmp(var,"If-Modified-Since")) {
				LOGS(L_PROXY,L_CHILD,("<- If-Modified-Since: %s\n", val));
			} else if (0 == strcasecmp(var,"Connection")) {
				LOGS(L_PROXY,L_CHILD,("<- Connection: %s\n", val));
				if (0 == strcasecmp(val, "keep-alive")) {
					keep_alive = 1;
					LOGS(L_PROXY,L_CHILD,("Connection: keep-alive\n"));
				} else if (0 == strcasecmp(val, "close")) {
					keep_alive = 0;
					LOGS(L_PROXY,L_CHILD,("Connection: close\n"));
				}
			} else if (0 == strcasecmp(var,"Keep-alive")) {
				LOGS(L_PROXY,L_CHILD,("Keep-Alive: %s\n", val));
				keep_alive_count = strtoul(val, NULL, 0);
				if (keep_alive_count > 0) {
					keep_alive = 1;
				} else {
					keep_alive = 0;
				}
			} else if (0 == strcasecmp(var,"Cache-control")) {
				LOGS(L_PROXY,L_CHILD,("<- Cache-control: %s\n", val));
			} else if (0 == strcasecmp(var,"Pragma")) {
				LOGS(L_PROXY,L_CHILD,("<- Pragma: %s\n", val));
			} else if (0 == strcasecmp(var,"Host")) {
				LOGS(L_PROXY,L_CHILD,("<- Host: %s\n", val));
				xfree(host);
				size = strlen("http://") + strlen(val) + strlen("/") + 1;
				host = xcalloc(size, sizeof(char));
				pm_snprintf(host, size, "http://%s/", val);
			} else if (0 == strcasecmp(var,"Content-type")) {
				LOGS(L_PROXY,L_CHILD,("<- Content-type: %s\n", val));
				xfree(reply->content_type);
				reply->content_type = xstrdup(val);
			} else if (0 == strcasecmp(var,"Content-length")) {
				LOGS(L_PROXY,L_CHILD,("<- Content-length: %s\n", val));
				reply->content_length = strtoul(val, NULL, 0);
			} else {
				xfree(toke);
				toke = xstrdup(line);
				var = toke;
				eol = strchr(var, '\r');
				if (NULL != eol) *eol = '\0';
				eol = strchr(var, '\n');
				if (NULL != eol) *eol = '\0';
				val = strchr(var, ' ');
				if (NULL != val) {
					*val++ = '\0';
					while (*val) {
						if (*val != '\t' &&
							*val != '\r' &&
							*val != '\n' &&
							*val != ' ') break;
						val++;
					}
				}
				if (0 == strcasecmp(var,"GET")) {
					LOGS(L_PROXY,L_CHILD,("<- %s %s\n", var, val));
					reply->request_type = HTTP_GET;
					query = xstrdup(strtok(val, "  \r\n"));
					proto = xstrdup(strtok(NULL, "  \r\n"));
				} else if (0 == strcasecmp(var,"HEAD")) {
					LOGS(L_PROXY,L_CHILD,("<- %s %s\n", var, val));
					reply->request_type = HTTP_HEAD;
					query = xstrdup(strtok(val, "  \r\n"));
					proto = xstrdup(strtok(NULL, "  \r\n"));
				} else if (0 == strcasecmp(var,"POST")) {
					LOGS(L_PROXY,L_CHILD,("<- %s %s\n", var, val));
					reply->request_type = HTTP_POST;
					query = xstrdup(strtok(val, "  \r\n"));
					proto = xstrdup(strtok(NULL, "  \r\n"));
				} else {
					LOGS(L_PROXY,L_CHILD,("<- *** %s\n", line));
				}
			}
			xfree(line);
			xfree(toke);
		}

		if (0 != rc)
			goto bailout;

		if (NULL == host) {
			if (0 == strcmp(g_conf->proxyhost, "0.0.0.0")) {
				size = strlen("http://") +
					strlen("127.0.0.1") + 1 + 11 + 1 + 1;
				host = xcalloc(size, sizeof(char));
				pm_snprintf(host, size, "http://%s:%d/",
					"127.0.0.1", (int)g_conf->proxyport);
			} else {
				size = strlen("http://") +
					strlen(g_conf->proxyhost) + 1 + 11 + 1 + 1;
				host = xcalloc(size, sizeof(char));
				pm_snprintf(host, size, "http://%s:%d/",
					g_conf->proxyhost, (int)g_conf->proxyport);
			}
		}
		xfree(proto);

		if (NONE == reply->request_type) {
			LOGS(L_PROXY,L_ERROR,("no request header detected\n"));
			goto bailout;
		}

		/* if this is a POST header, parse and insert the data */
		if (HTTP_POST == reply->request_type) {
			LOGS(L_PROXY,L_MINOR,("proxy child %s:%d POST %s\n",
				inet_ntoa(conn->address.sin_addr),
				ntohs(conn->address.sin_port),
				query ? query : "NULL"));
			if (NULL == reply->content_type) {
				rc = proxy_400(conn,
					LO("No 'Content-type:' found in HTTP header"));
				goto bailout;
			}
			if (0 == reply->content_length) {
				rc = proxy_400(conn,
					LO("No 'Content-length:' found in HTTP header"));
				goto bailout;
			}
			LOGS(L_PROXY,L_MINOR,("POST content-type:%s, content-length:%x\n",
				reply->content_type, (unsigned)reply->content_length));
			rc = proxy_post(conn, url);
			goto bailout;
		}

		/* check if this is a query for the default gateway page */
		if (NULL == query ||
			0 == strcmp(query, "/") ||
			0 == strcmp(query, "/index.html")) {

			rc = proxy_gateway(conn);
			goto bailout;
		}

		if (HTTP_GET == reply->request_type) {
			LOGS(L_PROXY,L_MINOR,("proxy child %s:%d GET %s\n",
				inet_ntoa(conn->address.sin_addr),
				ntohs(conn->address.sin_port),
				query));
		} else if (HTTP_HEAD == reply->request_type) {
			LOGS(L_PROXY,L_MINOR,("proxy child %s:%d HEAD %s\n",
				inet_ntoa(conn->address.sin_addr),
				ntohs(conn->address.sin_port),
				query));
		} else {
			LOGS(L_PROXY,L_MINOR,("proxy child %s:%d UNKNOWN %s\n",
				inet_ntoa(conn->address.sin_addr),
				ntohs(conn->address.sin_port),
				query ? query : "NULL"));
		}

		xfree(url);
		url = xstrdup(query);

		memset(keys, 0, sizeof(keys));
		memset(vals, 0, sizeof(vals));
		url_parse(keys, vals, MAXVARS, url);

		/* set password_valid=1 if no password is configured */
		password_valid = (strlen(g_conf->password) > 0) ? 0 : 1;
		/* check if there's a password in the vars and compare it */
		for (i = 0; 0 == password_valid && i < MAXVARS; i++) {
			if (NULL == keys[i]) {
				continue;
			}
			if (0 == strcasecmp(keys[i], "password")) {
				if (NULL != vals[i] && strlen(vals[i]) > 0 &&
					0 == strcmp(vals[i], g_conf->password)) {
					password_valid = 1;
				}
			}
		}

		/* scan vars */
		for (i = 0; i < MAXVARS; i++) {
			if (NULL == keys[i])
				break;
			if (0 == strcasecmp(keys[i], "key")) {
				xfree(reply->location);
				if (NULL != vals[i]) {
					size = strlen(host) + strlen(vals[i]) + 1;
					xfree(redir);
					redir = xcalloc(size, sizeof(char));
					pm_snprintf(redir, size, "%s%s", host, vals[i]);
					xfree(reply->location);
					url_decode(&reply->location, redir);
				}
			} else if (0 == strcasecmp(keys[i], "chk")) {
				memset(&reply->key, 0, sizeof(reply->key));
				if (NULL != vals[i]) {
					is_valid_chk(&reply->key, vals[i]);
				}
			} else if (0 == strcasecmp(keys[i], "board")) {
				xfree(reply->board);
				if (NULL != vals[i])
					reply->board = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "from")) {
				xfree(reply->from);
				if (NULL != vals[i])
					reply->from = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "subject")) {
				xfree(reply->subject);
				if (NULL != vals[i])
					reply->subject = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "body")) {
				xfree(reply->body);
				if (NULL != vals[i])
					reply->body = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "channel")) {
				xfree(reply->chat_channel);
				if (NULL != vals[i])
					reply->chat_channel = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "sender")) {
				xfree(reply->chat_sender);
				if (NULL != vals[i])
					reply->chat_sender = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "color")) {
				if (NULL != vals[i])
					reply->chat_color = strtoul(vals[i], NULL, 16);
				else
					reply->chat_color = 0;
			} else if (0 == strcasecmp(keys[i], "payload")) {
				xfree(reply->chat_payload);
				if (NULL != vals[i])
					reply->chat_payload = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "date")) {
				parse_tm_date(&reply->date, vals[i]);
			} else if (0 == strcasecmp(keys[i], "time")) {
				parse_tm_time(&reply->date, vals[i]);
			} else if (0 == strcasecmp(keys[i], "file_up")) {
				reply->file_up = NULL;
				if (NULL != vals[i]) {
					/* address of the shm result code */
					if (1 != sscanf(vals[i], "%p", &reply->file_up)) {
						reply->file_up = (file_up_t *)INVALID_ADDR;
					} else if (0 != svalid(reply->file_up)) {
						LOGS(L_PROXY,L_ERROR,("invalid file_up=%p\n",
							reply->file_up));
						reply->file_up = (file_up_t *)INVALID_ADDR;
					} else {
						LOGS(L_PROXY,L_DEBUG,("file_up=%p\n",
							reply->file_up));
					}
				}
			} else if (0 == strcasecmp(keys[i], "file_dn")) {
				reply->file_dn = NULL;
				if (NULL != vals[i]) {
					/* address of the shm result code */
					if (1 != sscanf(vals[i], "%p", &reply->file_dn)) {
						reply->file_dn = (file_dn_t *)INVALID_ADDR;
					} else if (0 != svalid(reply->file_dn)) {
						LOGS(L_PROXY,L_ERROR,("invalid file_dn=%p\n",
							reply->file_dn));
						reply->file_dn = (file_dn_t *)INVALID_ADDR;
					} else {
						LOGS(L_PROXY,L_DEBUG,("file_dn=%p\n",
							reply->file_dn));
					}
				}
			} else if (0 == strcasecmp(keys[i], "news_post")) {
				reply->news_post = NULL;
				if (NULL != vals[i]) {
					/* address of the shm result code */
					if (1 != sscanf(vals[i], "%p", &reply->news_post)) {
						reply->news_post = (news_post_t *)INVALID_ADDR;
					} else if (0 != svalid(reply->news_post)) {
						LOGS(L_PROXY,L_DEBUG,("invalid news_post=%p\n",
							reply->news_post));
						reply->news_post = (news_post_t *)INVALID_ADDR;
					} else {
						LOGS(L_PROXY,L_DEBUG,("news_post=%p\n",
							reply->news_post));
					}
				}
			} else if (0 == strcasecmp(keys[i], "rescan")) {
				g_news->rescan = 0;
				if (NULL != vals[i]) {
					g_news->rescan = strtoul(vals[i], NULL, 0);
				}
			} else if (0 == strcasecmp(keys[i], "legend")) {
				reply->legend = 0;
				if (NULL != vals[i]) {
					reply->legend = strtoul(vals[i], NULL, 0);
				}
			} else if (0 == strcasecmp(keys[i], "htl")) {
				if (NULL != vals[i]) {
					reply->htl = strtoul(vals[i], NULL, 0);
					if (reply->htl < 0)
						reply->htl = 0;
					if (reply->htl > g_conf->maxhtl)
						reply->htl = g_conf->maxhtl;
				}
			} else if (0 == strcasecmp(keys[i], "try")) {
				if (NULL != vals[i]) {
					reply->retry_number = strtoul(vals[i], NULL, 0);
					if (reply->retry_number < 1)
						reply->retry_number = 1;
					if (reply->retry_number > 2) {
						reply->htl = reply->retry_number * g_conf->maxhtl / 10;
						if (reply->retry_number >= 10)
							reply->htl = g_conf->maxhtl / 2;
					}
				}
			} else if (0 == strcasecmp(keys[i], "meta")) {
				reply->meta = 1;
				if (NULL != vals[i])
					reply->meta = strtoul(vals[i], NULL, 0);
			} else if (0 == strcasecmp(keys[i], "mime")) {
				xfree(reply->content_type);
				if (NULL != vals[i])
					reply->content_type = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "charset")) {
				xfree(reply->charset);
				if (NULL != vals[i])
					reply->charset = xstrdup(vals[i]);
			} else if (0 == strcasecmp(keys[i], "lang")) {
				pm_snprintf(reply->language, sizeof(reply->language),
					"%s", g_conf->language);
				if (NULL != vals[i])
					pm_snprintf(reply->language, sizeof(reply->language),
						"%s", vals[i]);
			} else if (0 == strcasecmp(keys[i], "password")) {
				/* silently ignore the password here */
			} else if (0 == strcasecmp(keys[i], "submit")) {
				/* copy submit value to reply struct */
				pm_snprintf(reply->submit, sizeof(reply->submit),
					LO("submit"));
				if (NULL != vals[i])
					pm_snprintf(reply->submit, sizeof(reply->submit),
						"%s", vals[i]);
			} else {
				/* if there was a valid password -- and this key isn't it */
				if (password_valid && 0 != strcasecmp(keys[i], "password")) {
					/* set proxy configuration */
					if (0 != proxy_node_config_set(keys[i], vals[i])) {
						LOGS(L_PROXY,L_ERROR,("configuring '%s=%s' failed\n",
							keys[i], vals[i]));
					}
				} else if (NULL != vals[i]) {
					LOGS(L_PROXY,L_DEBUG,("***** %s=%s\n", keys[i], vals[i]));
				} else {
					LOGS(L_PROXY,L_DEBUG,("***** %s=(null)\n", keys[i]));
				}
			}
			xfree(keys[i]);
			xfree(vals[i]);
		}
		uri = url;
		if (*uri == '/')
			uri++;
		LOGS(L_PROXY,L_MINOR,("URI: %s\n", uri));
		if (0 == strncmp(url,"/node", 5) ||
			0 == strcmp(uri, "favicon.ico") ||
			0 == strcmp(uri, "robots.txt")) {
			rc = proxy_node(conn, uri);
		} else if (0 == strncmp(uri,"proxy.insert", 12)) {
			rc = proxy_insert(conn, url, NULL, NULL, 0);
		} else if (0 == strncmp(uri,"news.create", 11)) {
			rc = proxy_news_create(conn, uri);
		} else if (0 == strncmp(uri,"news.reply", 10)) {
			rc = proxy_news_reply(conn, uri);
		} else if (0 == strncmp(uri,"news.post", 9)) {
			rc = proxy_news_post(conn, uri);
		} else if (0 == strncmp(uri,"news.boards", 11)) {
			rc = proxy_news_boards(conn, uri);
		} else if (0 == strncmp(uri,"news", 4)) {
			rc = proxy_news(conn, uri);
		} else if (0 == strncmp(uri,"chat", 4)) {
			rc = proxy_chat(conn, uri);
		} else if (0 == strncmp(uri,"__CHECKED_FTP__", 15)) {
			rc = proxy_checked(conn, uri, "ftp");
		} else if (0 == strncmp(uri,"__CHECKED_GOPHER__", 18)) {
			rc = proxy_checked(conn, uri, "gopher");
		} else if (0 == strncmp(uri,"__CHECKED_HTTP__", 16)) {
			rc = proxy_checked(conn, uri, "http");
		} else if (0 == strncmp(uri,"__CHECKED_HTTPS__", 17)) {
			rc = proxy_checked(conn, uri, "https");
		} else if (0 == strncmp(uri,"__CHECKED_RTSP__", 16)) {
			rc = proxy_checked(conn, uri, "rtsp");
		} else if (NULL != reply->location) {
			rc = proxy_301(conn, uri);
		} else {
			rc = proxy_loop(conn, uri);
		}

		/* clean up reply fields for the next request */
		conn->temp = NULL;
		if (NULL != reply) {
			xfree(reply->stylesheet);
			xfree(reply->board);
			xfree(reply->from);
			xfree(reply->subject);
			xfree(reply->body);
			xfree(reply->content_type);
			xfree(reply->charset);
			xfree(reply->location);
			xfree(reply->chat_output);
			xfree(reply->chat_status);
			xfree(reply->chat_channel);
			xfree(reply->chat_sender);
			xfree(reply->chat_payload);
		}
		xfree(reply);

		if (0 != rc || 0 == keep_alive) {
			/* no more requests */
			break;
		}

		if (--keep_alive_count <= 0) {
			/* no more requests */
			break;
		}

		sk = conn->socket;
		if (-1 == sk || CONN_CONNECTED != conn->status) {
			/* socket was shut down */
			break;
		}
		memset(&tv, 0, sizeof(tv));
		tv.tv_sec = keep_alive_timeout;
		FD_ZERO(&rdfds);
		FD_SET(sk, &rdfds);
		if (select(sk + 1, &rdfds, NULL, NULL, &tv) <= 0) {
			/* selecting the read fds failed */
			break;
		}
		if (!FD_ISSET(sk, &rdfds)) {
			/* sk not in the read fds: timed out */
			break;
		}
	} while (0 == rc);

bailout:
	xfree(redir);
	xfree(url);
	xfree(query);
	xfree(proto);
	xfree(host);
	xfree(toke);
	xfree(line);
	conn->temp = NULL;
	if (NULL != reply) {
		xfree(reply->stylesheet);
		xfree(reply->board);
		xfree(reply->from);
		xfree(reply->subject);
		xfree(reply->body);
		xfree(reply->content_type);
		xfree(reply->charset);
		xfree(reply->location);
		xfree(reply->chat_output);
		xfree(reply->chat_status);
		xfree(reply->chat_channel);
		xfree(reply->chat_sender);
		xfree(reply->chat_payload);
	}
	xfree(reply);
	LOGS(L_PROXY,L_MINOR,("closing connection from %s:%d\n",
		inet_ntoa(conn->address.sin_addr),
		ntohs(conn->address.sin_port)));
	sock_shutdown(conn);
	osd_exit(rc);
}

static void proxy_exit(int sig)
{
	pid_t pid = getpid();
	FUN("proxy_exit");

	signal(sig, SIG_DFL);
	LOGS(L_PROXY,L_MINOR,("*** {%d} signal %s ***\n",
		(int)getpid(), signal_name(sig)));
	if (g_proxy_pid == pid) {
		g_proxy_pid = (pid_t)-1;
	} else {
		/* close all handles */
		closeall(0, -1);
	}
}

/* called from the accept() loop in sock_incoming() after new connection */
int proxy_accept(void)
{
	/* detect a shutdown condition */
	if ((pid_t)-1 == g_proxy_pid ||
		(pid_t)0 == g_proxy_pid) {
		return -1;
	}
	return 0;
}

int proxy(void)
{
	char *listener = NULL;
	size_t size;
	int rc = 0;
	FUN("proxy");

	rc = pm_asprintf(&listener, "proxy [%s:%d]",
		g_conf->proxyhost, g_conf->proxyport);
	if (-1 == rc) {
		LOGS(L_PROXY,L_ERROR,("pm_asprintf() call failed (%s)\n",
			strerror(errno)));
		return -1;
	}
	rc = 0;

	size = sizeof(proxy_t) + g_conf->proxycache * sizeof(proxy_cache_t);
	g_proxy = (proxy_t *)scalloc(size, 1);
	if (NULL == g_proxy) {
		LOGS(L_PROXY,L_ERROR,("failed to scalloc() proxy (size %d) (%s)\n",
			(int)size, strerror(errno)));
		return -1;
	}
	/* set PID of g_proxy shm to zero, so a forked child doesn't remove it */
	spset((caddr_t)g_proxy, 0);

	if (0 != (rc = osd_sem_init(&g_proxy->sem, 1, 1))) {
		LOGS(L_PROXY,L_ERROR,("osd_sem_init(%p,%d,%d) call failed (%s)\n",
			&g_proxy->sem, 1, 1, strerror(errno)));
		return -1;
	}

	switch (osd_fork2(listener, g_conf->niceness, -1)) {
	case -1:
		LOGS(L_PROXY,L_ERROR,("osd_fork2('%s',%d) failed (%s)\n",
			listener, (int)g_conf->niceness, strerror(errno)));
		xfree(listener);
		return -1;
	case 0:
		g_proxy_pid = getpid();
		/* capture signals */
		set_signal_handler(SIGHUP, proxy_exit);
		set_signal_handler(SIGINT, proxy_exit);
		set_signal_handler(SIGPIPE, proxy_exit);
		set_signal_handler(SIGALRM, proxy_exit);
		set_signal_handler(SIGTERM, proxy_exit);
		rc = sock_incoming(g_conf->proxyhost, g_conf->proxyport,
			0, proxy_child, proxy_accept, 0, listener);
		if (0 != rc) {
			LOGS(L_PROXY,L_ERROR,("sock_incoming(%s:%d,...) call failed (%s)\n",
				g_conf->proxyhost, g_conf->proxyport, strerror(errno)));
		}
		osd_exit(rc);
	}

	info("%s:%d ", g_conf->proxyhost, g_conf->proxyport);
	xfree(listener);
	return rc;
}
